Merge lp:~flaviamissi/goamz/goamz into lp:~gophers/goamz/trunk

Proposed by flaviamissi
Status: Needs review
Proposed branch: lp:~flaviamissi/goamz/goamz
Merge into: lp:~gophers/goamz/trunk
Diff against target: 2256 lines (+2105/-0) (has conflicts)
13 files modified
aws/aws.go (+9/-0)
ec2/ec2test/server.go (+15/-0)
ec2/sign.go (+7/-0)
elb/elb.go (+384/-0)
elb/elb_test.go (+317/-0)
elb/elbi_test.go (+300/-0)
elb/elbt_test.go (+185/-0)
elb/elbtest/server.go (+455/-0)
elb/export_test.go (+9/-0)
elb/response_test.go (+205/-0)
elb/sign.go (+35/-0)
elb/sign_test.go (+66/-0)
elb/suite_test.go (+118/-0)
Text conflict in ec2/ec2test/server.go
Text conflict in ec2/sign.go
To merge this branch: bzr merge lp:~flaviamissi/goamz/goamz
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+141371@code.launchpad.net

Description of the change

Amazon Elastic Load Balancing support

This change adds support for basic operations for Amazon's Elastic Load Balancing service

https://codereview.appspot.com/7014047/

To post a comment you must log in.
Revision history for this message
flaviamissi (flaviamissi) wrote :

Reviewers: mp+141371_code.launchpad.net,

Message:
Please take a look.

Description:
Amazon Elastic Load Balancing support

This change adds support for basic operations for Amazon's Elastic Load
Balancing service

https://code.launchpad.net/~flaviamissi/goamz/goamz/+merge/141371

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/7014047/

Affected files:
   A [revision details]
   M aws/aws.go
   M ec2/ec2.go
   M ec2/ec2test/filter.go
   M ec2/ec2test/server.go
   M ec2/sign.go
   A elb/elb.go
   A elb/elb_test.go
   A elb/elbi_test.go
   A elb/elbt_test.go
   A elb/elbtest/server.go
   A elb/export_test.go
   A elb/response_test.go
   A elb/sign.go
   A elb/sign_test.go
   A elb/suite_test.go
   M exp/mturk/mturk.go
   M exp/sdb/sdb.go

Revision history for this message
flaviamissi (flaviamissi) wrote :
Revision history for this message
flaviamissi (flaviamissi) wrote :
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Download full text (6.3 KiB)

Great work, Flavia. A few initial comments:

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go
File elb/elb.go (right):

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode24
elb/elb.go:24: // The creation of a Load Balancer may differ inside EC2
and VPC.
It's not clear what this means.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode27
elb/elb.go:27: type CreateLoadBalancer struct {
I think we can short names in this package. This is the elb package,
meaning Elastic Load Balancer. Everything here is about load balancers.

I suggest s/CreateLoadBalancer/Create/ here. Will provide further
suggestions on this line below.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode36
elb/elb.go:36: // Listener to configure in Load Balancer.
// A Listener represents a process that listens for client connection
// requests, and maps a port and protocol in the load balancer,
// to a port and protocol on the backend instances.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode47
elb/elb.go:47: // Response to a CreateLoadBalance request.
// CreateResp represents a response to a Create request.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode50
elb/elb.go:50: type CreateLoadBalancerResp struct {
s/CreateLoadBalancerResp/CreateResp/

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode58
elb/elb.go:58: // Creates a Load Balancer in Amazon.
// Create creates a new load balancer.

It creates in Amazon or whoever else the endpoint represents. We have
other docs like this, but we should fix them over time.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode61
elb/elb.go:61: func (elb *ELB) CreateLoadBalancer(options
*CreateLoadBalancer) (resp *CreateLoadBalancerResp, err error) {
s/CreateLoadBalancer/Create/g

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode70
elb/elb.go:70: // Deletes a Load Balancer.
// Delete deletes the load balancer with name.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode84
elb/elb.go:84:
// RegisterInstancesResp represents a response to a RegisterInstances
request.
//
// See http://goo.gl/x9hru for more details.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode86
elb/elb.go:86: InstanceIds []string
`xml:"RegisterInstancesWithLoadBalancerResult>Instances>member>InstanceId"`
OMG.. beautiful XML element name.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode89
elb/elb.go:89: // Register N instances with a given Load Balancer.
// RegisterInstances registers into the named load balancer all
// the EC2 instances with the provided instance ids.

Note how as a convention in Go code, the first word in a method
documentation is always the method name itself.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode92
elb/elb.go:92: func (elb *ELB)
RegisterInstancesWithLoadBalancer(instanceIds []string, lbName string)
(resp *RegisterInstancesResp, err error) {
s/RegisterInstancesWithLoadBalancer/RegisterInstances/

Also, can we please have the lbName name first in the parameter list?
The thing we're operating on generally comes firs...

Read more...

Revision history for this message
flaviamissi (flaviamissi) wrote :
Download full text (6.8 KiB)

Please take a look.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go
File elb/elb.go (right):

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode24
elb/elb.go:24: // The creation of a Load Balancer may differ inside EC2
and VPC.
On 2013/02/28 21:39:19, niemeyer wrote:
> It's not clear what this means.

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode27
elb/elb.go:27: type CreateLoadBalancer struct {
On 2013/02/28 21:39:19, niemeyer wrote:
> I think we can short names in this package. This is the elb package,
meaning
> Elastic Load Balancer. Everything here is about load balancers.

> I suggest s/CreateLoadBalancer/Create/ here. Will provide further
suggestions on
> this line below.

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode36
elb/elb.go:36: // Listener to configure in Load Balancer.
On 2013/02/28 21:39:19, niemeyer wrote:
> // A Listener represents a process that listens for client connection
> // requests, and maps a port and protocol in the load balancer,
> // to a port and protocol on the backend instances.

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode47
elb/elb.go:47: // Response to a CreateLoadBalance request.
On 2013/02/28 21:39:19, niemeyer wrote:
> // CreateResp represents a response to a Create request.

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode50
elb/elb.go:50: type CreateLoadBalancerResp struct {
On 2013/02/28 21:39:19, niemeyer wrote:
> s/CreateLoadBalancerResp/CreateResp/

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode58
elb/elb.go:58: // Creates a Load Balancer in Amazon.
On 2013/02/28 21:39:19, niemeyer wrote:
> // Create creates a new load balancer.

> It creates in Amazon or whoever else the endpoint represents. We have
other docs
> like this, but we should fix them over time.

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode61
elb/elb.go:61: func (elb *ELB) CreateLoadBalancer(options
*CreateLoadBalancer) (resp *CreateLoadBalancerResp, err error) {
On 2013/02/28 21:39:19, niemeyer wrote:
> s/CreateLoadBalancer/Create/g

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode70
elb/elb.go:70: // Deletes a Load Balancer.
On 2013/02/28 21:39:19, niemeyer wrote:
> // Delete deletes the load balancer with name.

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode84
elb/elb.go:84:
On 2013/02/28 21:39:19, niemeyer wrote:
> // RegisterInstancesResp represents a response to a RegisterInstances
request.
> //
> // See http://goo.gl/x9hru for more details.

Done.

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode86
elb/elb.go:86: InstanceIds []string
`xml:"RegisterInstancesWithLoadBalancerResult>Instances>member>InstanceId"`
On 2013/02/28 21:39:19, niemeyer wrote:
> OMG.. beautiful XML element name.

yeah.. :|

https://codereview.appspot.com/7014047/diff/6019/elb/elb.go#newcode89
elb/elb.go:89: // Register N instances with a given Load Balancer.
On 2013/02/28 21:39:19, niemeyer wrote:
> // RegisterInstances registers into the named load balancer all
> // the EC2 instances with...

Read more...

Unmerged revisions

35. By flaviamissi

fixed points raised in revision of patch

34. By flaviamissi

elb/elbtest: describeLoadBalancer now also lists LBs added via elbtest.NewLoadBalancer

33. By flaviamissi

elb: created ConfigureHealthCheck action

32. By flaviamissi

elb: created DescribeInstanceHealth action

31. By flaviamissi

elb: go fmt

30. By flaviamissi

elb/elbtest: fixing infinite loop and storing DNSName. By @fsouza

29. By flaviamissi

elb: created DescribeLoadBalancers action

28. By flaviamissi

Created elb package for Elastic Load Balancing support

27. By flaviamissi

ec2, exp: gofmt

26. By flaviamissi

aws: Added Elastic Load Balancing endpoint to regions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'aws/aws.go'
2--- aws/aws.go 2012-11-13 23:59:04 +0000
3+++ aws/aws.go 2013-03-08 18:46:22 +0000
4@@ -28,6 +28,7 @@
5 SNSEndpoint string
6 SQSEndpoint string
7 IAMEndpoint string
8+ ELBEndpoint string
9 }
10
11 var USEast = Region{
12@@ -41,6 +42,7 @@
13 "https://sns.us-east-1.amazonaws.com",
14 "https://sqs.us-east-1.amazonaws.com",
15 "https://iam.amazonaws.com",
16+ "https://elasticloadbalancing.amazonaws.com",
17 }
18
19 var USWest = Region{
20@@ -54,6 +56,7 @@
21 "https://sns.us-west-1.amazonaws.com",
22 "https://sqs.us-west-1.amazonaws.com",
23 "https://iam.amazonaws.com",
24+ "https://elasticloadbalancing.amazonaws.com",
25 }
26
27 var USWest2 = Region{
28@@ -67,6 +70,7 @@
29 "https://sns.us-west-2.amazonaws.com",
30 "https://sqs.us-west-2.amazonaws.com",
31 "https://iam.amazonaws.com",
32+ "https://elasticloadbalancing.amazonaws.com",
33 }
34
35 var EUWest = Region{
36@@ -80,6 +84,7 @@
37 "https://sns.eu-west-1.amazonaws.com",
38 "https://sqs.eu-west-1.amazonaws.com",
39 "https://iam.amazonaws.com",
40+ "https://elasticloadbalancing.amazonaws.com",
41 }
42
43 var APSoutheast = Region{
44@@ -93,6 +98,7 @@
45 "https://sns.ap-southeast-1.amazonaws.com",
46 "https://sqs.ap-southeast-1.amazonaws.com",
47 "https://iam.amazonaws.com",
48+ "https://elasticloadbalancing.amazonaws.com",
49 }
50
51 var APSoutheast2 = Region{
52@@ -106,6 +112,7 @@
53 "https://sns.ap-southeast-2.amazonaws.com",
54 "https://sqs.ap-southeast-2.amazonaws.com",
55 "https://iam.amazonaws.com",
56+ "https://elasticloadbalancing.amazonaws.com",
57 }
58
59 var APNortheast = Region{
60@@ -119,6 +126,7 @@
61 "https://sns.ap-northeast-1.amazonaws.com",
62 "https://sqs.ap-northeast-1.amazonaws.com",
63 "https://iam.amazonaws.com",
64+ "https://elasticloadbalancing.amazonaws.com",
65 }
66
67 var SAEast = Region{
68@@ -132,6 +140,7 @@
69 "https://sns.sa-east-1.amazonaws.com",
70 "https://sqs.sa-east-1.amazonaws.com",
71 "https://iam.amazonaws.com",
72+ "https://elasticloadbalancing.amazonaws.com",
73 }
74
75 var Regions = map[string]Region{
76
77=== modified file 'ec2/ec2test/server.go'
78--- ec2/ec2test/server.go 2013-01-31 17:06:10 +0000
79+++ ec2/ec2test/server.go 2013-03-08 18:46:22 +0000
80@@ -417,6 +417,7 @@
81 }
82
83 // TODO attributes still to consider:
84+<<<<<<< TREE
85 // ImageId: accept anything, we can verify later
86 // KeyName ?
87 // InstanceType ?
88@@ -429,6 +430,20 @@
89 // DisableAPITermination bool
90 // ShutdownBehavior string
91 // PrivateIPAddress string
92+=======
93+ // ImageId: accept anything, we can verify later
94+ // KeyName ?
95+ // InstanceType ?
96+ // KernelId ?
97+ // RamdiskId ?
98+ // AvailZone ?
99+ // GroupName tag
100+ // Monitoring ignore?
101+ // SubnetId ?
102+ // DisableAPITermination bool
103+ // ShutdownBehavior string
104+ // PrivateIPAddress string
105+>>>>>>> MERGE-SOURCE
106
107 srv.mu.Lock()
108 defer srv.mu.Unlock()
109
110=== modified file 'ec2/sign.go'
111--- ec2/sign.go 2013-01-31 17:06:10 +0000
112+++ ec2/sign.go 2013-03-08 18:46:22 +0000
113@@ -19,10 +19,17 @@
114 params["SignatureVersion"] = "2"
115 params["SignatureMethod"] = "HmacSHA256"
116
117+<<<<<<< TREE
118 // AWS specifies that the parameters in a signed request must
119 // be provided in the natural order of the keys. This is distinct
120 // from the natural order of the encoded value of key=value.
121 // Percent and equals affect the sorting order.
122+=======
123+ // AWS specifies that the parameters in a signed request must
124+ // be in natural order of the keys. This is distinct from the
125+ // natural order of the encoded value of key=value. Basically
126+ // percent and equals affect the sorting order.
127+>>>>>>> MERGE-SOURCE
128 var keys, sarray []string
129 for k, _ := range params {
130 keys = append(keys, k)
131
132=== added directory 'elb'
133=== added file 'elb/elb.go'
134--- elb/elb.go 1970-01-01 00:00:00 +0000
135+++ elb/elb.go 2013-03-08 18:46:22 +0000
136@@ -0,0 +1,384 @@
137+// The elb package provides types and functions to interact Elastic Load Balancing service
138+package elb
139+
140+import (
141+ "encoding/xml"
142+ "fmt"
143+ "launchpad.net/goamz/aws"
144+ "launchpad.net/goamz/ec2"
145+ "net/http"
146+ "net/url"
147+ "strconv"
148+ "time"
149+)
150+
151+type ELB struct {
152+ aws.Auth
153+ aws.Region
154+}
155+
156+func New(auth aws.Auth, region aws.Region) *ELB {
157+ return &ELB{auth, region}
158+}
159+
160+// Create type encapsulates options for the CreateLoadBalancer request in AWS.
161+// The creation of a Load Balancer will differ inside a VPC, there are some specific fields
162+// used only on a VPC, e.g.: Scheme, Subnets and SecurityGroups.
163+//
164+// See http://goo.gl/4QFKi for more details.
165+type Create struct {
166+ Name string
167+ AvailZones []string
168+ Listeners []Listener
169+ Scheme string
170+ SecurityGroups []string
171+ Subnets []string
172+}
173+
174+// A Listener represents a process that listens for client connection
175+// requests, and maps a port and protocol in the load balancer,
176+// to a port and protocol on the backend instances.
177+//
178+// See http://goo.gl/NJQCj for more details.
179+type Listener struct {
180+ InstancePort int
181+ InstanceProtocol string
182+ LoadBalancerPort int
183+ Protocol string
184+ SSLCertificateId string
185+}
186+
187+// CreateResp represents a response to a Create request.
188+//
189+// See http://goo.gl/4QFKi for more details.
190+type CreateResp struct {
191+ DNSName string `xml:"CreateLoadBalancerResult>DNSName"`
192+}
193+
194+type SimpleResp struct {
195+ RequestId string `xml:"ResponseMetadata>RequestId"`
196+}
197+
198+// Create creates a Load Balancer.
199+//
200+// See http://goo.gl/4QFKi for more details.
201+func (elb *ELB) Create(options *Create) (resp *CreateResp, err error) {
202+ params := makeCreateParams(options)
203+ resp = new(CreateResp)
204+ if err := elb.query(params, resp); err != nil {
205+ return nil, err
206+ }
207+ return
208+}
209+
210+// Delete deletes a Load Balancer.
211+//
212+// See http://goo.gl/sDmPp for more details.
213+func (elb *ELB) Delete(name string) (resp *SimpleResp, err error) {
214+ params := map[string]string{
215+ "Action": "DeleteLoadBalancer",
216+ "LoadBalancerName": name,
217+ }
218+ resp = new(SimpleResp)
219+ if err := elb.query(params, resp); err != nil {
220+ return nil, err
221+ }
222+ return resp, nil
223+}
224+
225+// RegisterInstancesResp represents a response to a RegisterInstances request.
226+//
227+// See http://goo.gl/x9hru for more details.
228+type RegisterInstancesResp struct {
229+ InstanceIds []string `xml:"RegisterInstancesWithLoadBalancerResult>Instances>member>InstanceId"`
230+}
231+
232+// RegisterInstances registers into the named load balancer all
233+// the EC2 instances with the provided instance ids.
234+//
235+// See http://goo.gl/x9hru for more details.
236+func (elb *ELB) RegisterInstances(lbName string, instanceIds []string) (resp *RegisterInstancesResp, err error) {
237+ params := map[string]string{
238+ "Action": "RegisterInstancesWithLoadBalancer",
239+ "LoadBalancerName": lbName,
240+ }
241+ for i, instanceId := range instanceIds {
242+ key := fmt.Sprintf("Instances.member.%d.InstanceId", i+1)
243+ params[key] = instanceId
244+ }
245+ resp = new(RegisterInstancesResp)
246+ if err := elb.query(params, resp); err != nil {
247+ return nil, err
248+ }
249+ return resp, nil
250+}
251+
252+// DeregisterInstances drops from the named load balancer all
253+// the EC2 instances with the provided instance ids.
254+//
255+// See http://goo.gl/Hgo4U for more details.
256+func (elb *ELB) DeregisterInstances(lbName string, instanceIds []string) (resp *SimpleResp, err error) {
257+ params := map[string]string{
258+ "Action": "DeregisterInstancesFromLoadBalancer",
259+ "LoadBalancerName": lbName,
260+ }
261+ for i, instanceId := range instanceIds {
262+ key := fmt.Sprintf("Instances.member.%d.InstanceId", i+1)
263+ params[key] = instanceId
264+ }
265+ resp = new(SimpleResp)
266+ if err := elb.query(params, resp); err != nil {
267+ return nil, err
268+ }
269+ return resp, nil
270+}
271+
272+type DescribeResp struct {
273+ LoadBalancerDescriptions []LoadBalancerDescription `xml:"DescribeLoadBalancersResult>LoadBalancerDescriptions>member"`
274+}
275+
276+type LoadBalancerDescription struct {
277+ AvailZones []string `xml:"AvailabilityZones>member"`
278+ BackendServerDescriptions []BackendServerDescriptions `xml:"BackendServerDescriptions>member"`
279+ CanonicalHostedZoneName string `xml:"CanonicalHostedZoneName"`
280+ CanonicalHostedZoneNameId string `xml:"CanonicalHostedZoneNameID"`
281+ CreatedTime time.Time `xml:"CreatedTime"`
282+ DNSName string `xml:"DNSName"`
283+ HealthCheck HealthCheck `xml:"HealthCheck"`
284+ Instances []ec2.Instance `xml:"Instances>member"`
285+ ListenerDescriptions []ListenerDescription `xml:"ListenerDescriptions>member"`
286+ Name string `xml:"LoadBalancerName"`
287+ Policies Policies `xml:"Policies"`
288+ Scheme string `xml:"Scheme"`
289+ SecurityGroups []string `xml:"SecurityGroups>member"`
290+ SourceSecurityGroup SourceSecurityGroup `xml:"SourceSecurityGroup"`
291+ Subnets []string `xml:"Subnets>member"`
292+ VPCId string `xml:"VPCId"`
293+}
294+
295+// DescribeLoadBalancers describes Load Balancers.
296+// It can be used to describe all Load Balancers or specific ones by its names.
297+//
298+// See http://goo.gl/wofJA for more details.
299+func (elb *ELB) DescribeLoadBalancers(names ...string) (*DescribeResp, error) {
300+ params := map[string]string{"Action": "DescribeLoadBalancers"}
301+ for i, name := range names {
302+ index := fmt.Sprintf("LoadBalancerNames.member.%d", i+1)
303+ params[index] = name
304+ }
305+ resp := new(DescribeResp)
306+ if err := elb.query(params, resp); err != nil {
307+ return nil, err
308+ }
309+ return resp, nil
310+}
311+
312+// BackendServerDescriptions type is used as a response element in the DescribeLoadBalancers action
313+// to describe the configuration of the back-end server
314+type BackendServerDescriptions struct {
315+ InstancePort int `xml:"InstancePort"`
316+ PolicyNames []string `xml:"PolicyNames>member"`
317+}
318+
319+// HealthCheck represents a health check for a LB.
320+//
321+// See http://goo.gl/G08Z9 for more details.
322+type HealthCheck struct {
323+ HealthyThreshold int `xml:"HealthyThreshold"`
324+ Interval int `xml:"Interval"`
325+ Target string `xml:"Target"`
326+ Timeout int `xml:"Timeout"`
327+ UnhealthyThreshold int `xml:"UnhealthyThreshold"`
328+}
329+
330+type ListenerDescription struct {
331+ Listener Listener `xml:"Listener"`
332+ PolicyNames []string `xml:"PolicyNames>member"`
333+}
334+
335+type Policies struct {
336+ AppCookieStickinessPolicies []AppCookieStickinessPolicies `xml:"AppCookieStickinessPolicies>member"`
337+ LBCookieStickinessPolicies []LBCookieStickinessPolicies `xml:"LBCookieStickinessPolicies>member"`
338+ OtherPolicies []string `xml:"OtherPolicies>member"`
339+}
340+
341+// AppCookieStickinessPolicies type encapsulates application
342+// cookie stickiness policies of a LB.
343+//
344+// See http://goo.gl/clXGV for more information.
345+type AppCookieStickinessPolicies struct {
346+ CookieName string `xml:"CookieName"`
347+ PolicyName string `xml:"PolicyName"`
348+}
349+
350+// LBCookieStickinessPolicies type encapsulates load balancer cookie stickiness policies.
351+//
352+// See http://goo.gl/kOjre for more information.
353+type LBCookieStickinessPolicies struct {
354+ CookieExpirationPeriod int `xml:"CookieExpirationPeriod"`
355+ PolicyName string `xml:"PolicyName"`
356+}
357+
358+type SourceSecurityGroup struct {
359+ GroupName string `xml:"GroupName"`
360+ OwnerAlias string `xml:"OwnerAlias"`
361+}
362+
363+// DescribeInstanceHealthResp type represents a XML response for DescribeInstanceHealth action.
364+//
365+// See http://goo.gl/ovIB1 for more information.
366+type DescribeInstanceHealthResp struct {
367+ InstanceStates []InstanceState `xml:"DescribeInstanceHealthResult>InstanceStates>member"`
368+}
369+
370+// InstanceState type represents the instance state of an instance returned in
371+// a DescribeInstanceHealth action.
372+//
373+// See http://goo.gl/dzWfP for more information.
374+type InstanceState struct {
375+ Description string `xml:"Description"`
376+ InstanceId string `xml:"InstanceId"`
377+ ReasonCode string `xml:"ReasonCode"`
378+ State string `xml:"State"`
379+}
380+
381+// DescribeInstanceHealth describes current state of the instances of the specified LoadBalancer.
382+// If no instances are specified, the state of all the instances for the LoadBalancer is returned
383+//
384+// See http://goo.gl/ovIB1 for more information.
385+func (elb *ELB) DescribeInstanceHealth(lbName string, instanceIds ...string) (*DescribeInstanceHealthResp, error) {
386+ params := map[string]string{
387+ "Action": "DescribeInstanceHealth",
388+ "LoadBalancerName": lbName,
389+ }
390+ for _, iId := range instanceIds {
391+ key := fmt.Sprintf("Instances.member.1.InstanceId")
392+ params[key] = iId
393+ }
394+ resp := new(DescribeInstanceHealthResp)
395+ if err := elb.query(params, resp); err != nil {
396+ return nil, err
397+ }
398+ return resp, nil
399+}
400+
401+// Type HealthCheckResp encapsulates a response for ConfigureHealthCheck action.
402+type HealthCheckResp struct {
403+ HealthCheck *HealthCheck `xml:"ConfigureHealthCheckResult>HealthCheck"`
404+}
405+
406+// ConfigureHealthCheck configures an application health check for the
407+// instances on the given LB.
408+//
409+// See http://goo.gl/2HE6a for more information
410+func (elb *ELB) ConfigureHealthCheck(lbName string, healthCheck *HealthCheck) (*HealthCheckResp, error) {
411+ params := map[string]string{
412+ "Action": "ConfigureHealthCheck",
413+ "LoadBalancerName": lbName,
414+ "HealthCheck.HealthyThreshold": strconv.Itoa(healthCheck.HealthyThreshold),
415+ "HealthCheck.Interval": strconv.Itoa(healthCheck.Interval),
416+ "HealthCheck.Target": healthCheck.Target,
417+ "HealthCheck.Timeout": strconv.Itoa(healthCheck.Timeout),
418+ "HealthCheck.UnhealthyThreshold": strconv.Itoa(healthCheck.UnhealthyThreshold),
419+ }
420+ resp := new(HealthCheckResp)
421+ if err := elb.query(params, resp); err != nil {
422+ return nil, err
423+ }
424+ return resp, nil
425+}
426+
427+func (elb *ELB) query(params map[string]string, resp interface{}) error {
428+ params["Version"] = "2012-06-01"
429+ params["Timestamp"] = time.Now().In(time.UTC).Format(time.RFC3339)
430+ endpoint, err := url.Parse(elb.Region.ELBEndpoint)
431+ if err != nil {
432+ return err
433+ }
434+ if endpoint.Path == "" {
435+ endpoint.Path = "/"
436+ }
437+ sign(elb.Auth, "GET", endpoint.Path, params, endpoint.Host)
438+ endpoint.RawQuery = multimap(params).Encode()
439+ r, err := http.Get(endpoint.String())
440+ if err != nil {
441+ return err
442+ }
443+ defer r.Body.Close()
444+ if r.StatusCode != 200 {
445+ return buildError(r)
446+ }
447+ return xml.NewDecoder(r.Body).Decode(resp)
448+}
449+
450+// Error encapsulates an error returned by ELB.
451+type Error struct {
452+ // HTTP status code
453+ StatusCode int
454+ // AWS error code
455+ Code string
456+ // The human-oriented error message
457+ Message string
458+}
459+
460+func (err *Error) Error() string {
461+ if err.Code == "" {
462+ return err.Message
463+ }
464+
465+ return fmt.Sprintf("%s (%s)", err.Message, err.Code)
466+}
467+
468+type xmlErrors struct {
469+ Errors []Error `xml:"Error"`
470+}
471+
472+func buildError(r *http.Response) error {
473+ var (
474+ err Error
475+ errors xmlErrors
476+ )
477+ xml.NewDecoder(r.Body).Decode(&errors)
478+ if len(errors.Errors) > 0 {
479+ err = errors.Errors[0]
480+ }
481+ err.StatusCode = r.StatusCode
482+ if err.Message == "" {
483+ err.Message = r.Status
484+ }
485+ return &err
486+}
487+
488+func multimap(p map[string]string) url.Values {
489+ q := make(url.Values, len(p))
490+ for k, v := range p {
491+ q[k] = []string{v}
492+ }
493+ return q
494+}
495+
496+func makeCreateParams(createLB *Create) map[string]string {
497+ params := make(map[string]string)
498+ params["LoadBalancerName"] = createLB.Name
499+ params["Action"] = "CreateLoadBalancer"
500+ if createLB.Scheme != "" {
501+ params["Scheme"] = createLB.Scheme
502+ }
503+ for i, s := range createLB.Subnets {
504+ key := fmt.Sprintf("Subnets.member.%d", i+1)
505+ params[key] = s
506+ }
507+ for i, l := range createLB.Listeners {
508+ key := "Listeners.member.%d.%s"
509+ index := i + 1
510+ params[fmt.Sprintf(key, index, "InstancePort")] = strconv.Itoa(l.InstancePort)
511+ params[fmt.Sprintf(key, index, "InstanceProtocol")] = l.InstanceProtocol
512+ params[fmt.Sprintf(key, index, "Protocol")] = l.Protocol
513+ params[fmt.Sprintf(key, index, "LoadBalancerPort")] = strconv.Itoa(l.LoadBalancerPort)
514+ }
515+ for i, az := range createLB.AvailZones {
516+ key := fmt.Sprintf("AvailabilityZones.member.%d", i+1)
517+ params[key] = az
518+ }
519+ return params
520+}
521
522=== added file 'elb/elb_test.go'
523--- elb/elb_test.go 1970-01-01 00:00:00 +0000
524+++ elb/elb_test.go 2013-03-08 18:46:22 +0000
525@@ -0,0 +1,317 @@
526+package elb_test
527+
528+import (
529+ "launchpad.net/goamz/aws"
530+ "launchpad.net/goamz/ec2"
531+ "launchpad.net/goamz/elb"
532+ . "launchpad.net/gocheck"
533+ "time"
534+)
535+
536+type S struct {
537+ HTTPSuite
538+ elb *elb.ELB
539+}
540+
541+var _ = Suite(&S{})
542+
543+func (s *S) SetUpSuite(c *C) {
544+ s.HTTPSuite.SetUpSuite(c)
545+ auth := aws.Auth{"abc", "123"}
546+ s.elb = elb.New(auth, aws.Region{ELBEndpoint: testServer.URL})
547+}
548+
549+func (s *S) TestCreate(c *C) {
550+ testServer.PrepareResponse(200, nil, Create)
551+ testServer.PrepareResponse(200, nil, Delete)
552+ createLB := &elb.Create{
553+ Name: "testlb",
554+ AvailZones: []string{"us-east-1a", "us-east-1b"},
555+ Listeners: []elb.Listener{
556+ {
557+ InstancePort: 80,
558+ InstanceProtocol: "http",
559+ Protocol: "http",
560+ LoadBalancerPort: 80,
561+ },
562+ },
563+ }
564+ resp, err := s.elb.Create(createLB)
565+ c.Assert(err, IsNil)
566+ defer s.elb.Delete(createLB.Name)
567+ values := testServer.WaitRequest().URL.Query()
568+ c.Assert(values.Get("Version"), Equals, "2012-06-01")
569+ c.Assert(values.Get("Action"), Equals, "CreateLoadBalancer")
570+ c.Assert(values.Get("Timestamp"), Not(Equals), "")
571+ c.Assert(values.Get("LoadBalancerName"), Equals, "testlb")
572+ c.Assert(values.Get("AvailabilityZones.member.1"), Equals, "us-east-1a")
573+ c.Assert(values.Get("AvailabilityZones.member.2"), Equals, "us-east-1b")
574+ c.Assert(values.Get("Listeners.member.1.InstancePort"), Equals, "80")
575+ c.Assert(values.Get("Listeners.member.1.InstanceProtocol"), Equals, "http")
576+ c.Assert(values.Get("Listeners.member.1.Protocol"), Equals, "http")
577+ c.Assert(values.Get("Listeners.member.1.LoadBalancerPort"), Equals, "80")
578+ c.Assert(values.Get("Signature"), Not(Equals), "")
579+ c.Assert(resp.DNSName, Equals, "testlb-339187009.us-east-1.elb.amazonaws.com")
580+}
581+
582+func (s *S) TestCreateWithSubnetsAndMoreListeners(c *C) {
583+ testServer.PrepareResponse(200, nil, Create)
584+ testServer.PrepareResponse(200, nil, Delete)
585+ createLB := &elb.Create{
586+ Name: "testlb",
587+ Listeners: []elb.Listener{
588+ {
589+ InstancePort: 80,
590+ InstanceProtocol: "http",
591+ Protocol: "http",
592+ LoadBalancerPort: 80,
593+ },
594+ {
595+ InstancePort: 8080,
596+ InstanceProtocol: "http",
597+ Protocol: "http",
598+ LoadBalancerPort: 8080,
599+ },
600+ },
601+ Subnets: []string{"subnetid-1", "subnetid-2"},
602+ }
603+ _, err := s.elb.Create(createLB)
604+ c.Assert(err, IsNil)
605+ defer s.elb.Delete(createLB.Name)
606+ values := testServer.WaitRequest().URL.Query()
607+ c.Assert(values.Get("Listeners.member.1.InstancePort"), Equals, "80")
608+ c.Assert(values.Get("Listeners.member.1.LoadBalancerPort"), Equals, "80")
609+ c.Assert(values.Get("Listeners.member.2.InstancePort"), Equals, "8080")
610+ c.Assert(values.Get("Listeners.member.2.LoadBalancerPort"), Equals, "8080")
611+ c.Assert(values.Get("Subnets.member.1"), Equals, "subnetid-1")
612+ c.Assert(values.Get("Subnets.member.2"), Equals, "subnetid-2")
613+}
614+
615+func (s *S) TestCreateWithWrongParamsCombination(c *C) {
616+ testServer.PrepareResponse(400, nil, CreateBalancerBadRequest)
617+ createLB := &elb.Create{
618+ Name: "testlb",
619+ AvailZones: []string{"us-east-1a", "us-east-1b"},
620+ Listeners: []elb.Listener{
621+ {
622+ InstancePort: 80,
623+ InstanceProtocol: "http",
624+ Protocol: "http",
625+ LoadBalancerPort: 80,
626+ },
627+ },
628+ Subnets: []string{"subnetid-1", "subnetid2"},
629+ }
630+ resp, err := s.elb.Create(createLB)
631+ c.Assert(resp, IsNil)
632+ c.Assert(err, NotNil)
633+ e, ok := err.(*elb.Error)
634+ c.Assert(ok, Equals, true)
635+ c.Assert(e.Message, Equals, "Only one of SubnetIds or AvailabilityZones may be specified")
636+ c.Assert(e.Code, Equals, "ValidationError")
637+}
638+
639+func (s *S) TestDelete(c *C) {
640+ testServer.PrepareResponse(200, nil, Delete)
641+ resp, err := s.elb.Delete("testlb")
642+ c.Assert(err, IsNil)
643+ values := testServer.WaitRequest().URL.Query()
644+ c.Assert(values.Get("Version"), Equals, "2012-06-01")
645+ c.Assert(values.Get("Signature"), Not(Equals), "")
646+ c.Assert(values.Get("Timestamp"), Not(Equals), "")
647+ c.Assert(values.Get("Action"), Equals, "DeleteLoadBalancer")
648+ c.Assert(values.Get("LoadBalancerName"), Equals, "testlb")
649+ c.Assert(resp.RequestId, Equals, "8d7223db-49d7-11e2-bba9-35ba56032fe1")
650+}
651+
652+func (s *S) TestRegisterInstances(c *C) {
653+ testServer.PrepareResponse(200, nil, RegisterInstances)
654+ resp, err := s.elb.RegisterInstances("testlb", []string{"i-b44db8ca", "i-461ecf38"})
655+ c.Assert(err, IsNil)
656+ values := testServer.WaitRequest().URL.Query()
657+ c.Assert(values.Get("Version"), Equals, "2012-06-01")
658+ c.Assert(values.Get("Signature"), Not(Equals), "")
659+ c.Assert(values.Get("Timestamp"), Not(Equals), "")
660+ c.Assert(values.Get("Action"), Equals, "RegisterInstancesWithLoadBalancer")
661+ c.Assert(values.Get("LoadBalancerName"), Equals, "testlb")
662+ c.Assert(values.Get("Instances.member.1.InstanceId"), Equals, "i-b44db8ca")
663+ c.Assert(values.Get("Instances.member.2.InstanceId"), Equals, "i-461ecf38")
664+ c.Assert(resp.InstanceIds, DeepEquals, []string{"i-b44db8ca", "i-461ecf38"})
665+}
666+
667+func (s *S) TestRegisterInstancesBalancerBadRequest(c *C) {
668+ testServer.PrepareResponse(400, nil, RegisterInstancesBadRequest)
669+ resp, err := s.elb.RegisterInstances("absentLB", []string{"i-b44db8ca", "i-461ecf38"})
670+ c.Assert(resp, IsNil)
671+ c.Assert(err, NotNil)
672+ e, ok := err.(*elb.Error)
673+ c.Assert(ok, Equals, true)
674+ c.Assert(e.Message, Equals, "There is no ACTIVE Load Balancer named 'absentLB'")
675+ c.Assert(e.Code, Equals, "LoadBalancerNotFound")
676+}
677+
678+func (s *S) TestDeregisterInstances(c *C) {
679+ testServer.PrepareResponse(200, nil, DeregisterInstances)
680+ resp, err := s.elb.DeregisterInstances("testlb", []string{"i-b44db8ca", "i-461ecf38"})
681+ c.Assert(err, IsNil)
682+ values := testServer.WaitRequest().URL.Query()
683+ c.Assert(values.Get("Version"), Equals, "2012-06-01")
684+ c.Assert(values.Get("Signature"), Not(Equals), "")
685+ c.Assert(values.Get("Timestamp"), Not(Equals), "")
686+ c.Assert(values.Get("Action"), Equals, "DeregisterInstancesFromLoadBalancer")
687+ c.Assert(values.Get("LoadBalancerName"), Equals, "testlb")
688+ c.Assert(values.Get("Instances.member.1.InstanceId"), Equals, "i-b44db8ca")
689+ c.Assert(values.Get("Instances.member.2.InstanceId"), Equals, "i-461ecf38")
690+ c.Assert(resp.RequestId, Equals, "d6490837-49fd-11e2-bba9-35ba56032fe1")
691+}
692+
693+func (s *S) TestDeregisterInstancesBalancerBadRequest(c *C) {
694+ testServer.PrepareResponse(400, nil, DeregisterInstancesBadRequest)
695+ resp, err := s.elb.DeregisterInstances("testlb", []string{"i-b44db8ca", "i-461ecf38"})
696+ c.Assert(resp, IsNil)
697+ c.Assert(err, NotNil)
698+ e, ok := err.(*elb.Error)
699+ c.Assert(ok, Equals, true)
700+ c.Assert(e.Message, Equals, "There is no ACTIVE Load Balancer named 'absentlb'")
701+ c.Assert(e.Code, Equals, "LoadBalancerNotFound")
702+}
703+
704+func (s *S) TestDescribeLoadBalancers(c *C) {
705+ testServer.PrepareResponse(200, nil, DescribeLoadBalancers)
706+ resp, err := s.elb.DescribeLoadBalancers()
707+ c.Assert(err, IsNil)
708+ values := testServer.WaitRequest().URL.Query()
709+ c.Assert(values.Get("Version"), Equals, "2012-06-01")
710+ c.Assert(values.Get("Signature"), Not(Equals), "")
711+ c.Assert(values.Get("Timestamp"), Not(Equals), "")
712+ c.Assert(values.Get("Action"), Equals, "DescribeLoadBalancers")
713+ t, _ := time.Parse(time.RFC3339, "2012-12-27T11:51:52.970Z")
714+ expected := &elb.DescribeResp{
715+ []elb.LoadBalancerDescription{
716+ {
717+ AvailZones: []string{"us-east-1a"},
718+ BackendServerDescriptions: []elb.BackendServerDescriptions(nil),
719+ CanonicalHostedZoneName: "testlb-2087227216.us-east-1.elb.amazonaws.com",
720+ CanonicalHostedZoneNameId: "Z3DZXE0Q79N41H",
721+ CreatedTime: t,
722+ DNSName: "testlb-2087227216.us-east-1.elb.amazonaws.com",
723+ HealthCheck: elb.HealthCheck{
724+ HealthyThreshold: 10,
725+ Interval: 30,
726+ Target: "TCP:80",
727+ Timeout: 5,
728+ UnhealthyThreshold: 2,
729+ },
730+ Instances: []ec2.Instance(nil),
731+ ListenerDescriptions: []elb.ListenerDescription{
732+ {
733+ Listener: elb.Listener{
734+ Protocol: "HTTP",
735+ LoadBalancerPort: 80,
736+ InstanceProtocol: "HTTP",
737+ InstancePort: 80,
738+ },
739+ },
740+ },
741+ Name: "testlb",
742+ Scheme: "internet-facing",
743+ SecurityGroups: []string(nil),
744+ SourceSecurityGroup: elb.SourceSecurityGroup{
745+ GroupName: "amazon-elb-sg",
746+ OwnerAlias: "amazon-elb",
747+ },
748+ Subnets: []string(nil),
749+ },
750+ },
751+ }
752+ c.Assert(resp, DeepEquals, expected)
753+}
754+
755+func (s *S) TestDescribeLoadBalancersByName(c *C) {
756+ testServer.PrepareResponse(200, nil, DescribeLoadBalancers)
757+ s.elb.DescribeLoadBalancers("somelb")
758+ values := testServer.WaitRequest().URL.Query()
759+ c.Assert(values.Get("Version"), Equals, "2012-06-01")
760+ c.Assert(values.Get("Signature"), Not(Equals), "")
761+ c.Assert(values.Get("Timestamp"), Not(Equals), "")
762+ c.Assert(values.Get("Action"), Equals, "DescribeLoadBalancers")
763+ c.Assert(values.Get("LoadBalancerNames.member.1"), Equals, "somelb")
764+}
765+
766+func (s *S) TestDescribeLoadBalancersBadRequest(c *C) {
767+ testServer.PrepareResponse(400, nil, DescribeLoadBalancersBadRequest)
768+ resp, err := s.elb.DescribeLoadBalancers()
769+ c.Assert(resp, IsNil)
770+ c.Assert(err, NotNil)
771+ c.Assert(err, ErrorMatches, `^Cannot find Load Balancer absentlb \(LoadBalancerNotFound\)$`)
772+}
773+
774+func (s *S) TestDescribeInstanceHealth(c *C) {
775+ testServer.PrepareResponse(200, nil, DescribeInstanceHealth)
776+ resp, err := s.elb.DescribeInstanceHealth("testlb", "i-b44db8ca")
777+ c.Assert(err, IsNil)
778+ values := testServer.WaitRequest().URL.Query()
779+ c.Assert(values.Get("Version"), Equals, "2012-06-01")
780+ c.Assert(values.Get("Signature"), Not(Equals), "")
781+ c.Assert(values.Get("Timestamp"), Not(Equals), "")
782+ c.Assert(values.Get("Action"), Equals, "DescribeInstanceHealth")
783+ c.Assert(values.Get("LoadBalancerName"), Equals, "testlb")
784+ c.Assert(values.Get("Instances.member.1.InstanceId"), Equals, "i-b44db8ca")
785+ c.Assert(len(resp.InstanceStates) > 0, Equals, true)
786+ c.Assert(resp.InstanceStates[0].Description, Equals, "Instance registration is still in progress.")
787+ c.Assert(resp.InstanceStates[0].InstanceId, Equals, "i-b44db8ca")
788+ c.Assert(resp.InstanceStates[0].State, Equals, "OutOfService")
789+ c.Assert(resp.InstanceStates[0].ReasonCode, Equals, "ELB")
790+}
791+
792+func (s *S) TestDescribeInstanceHealthBadRequest(c *C) {
793+ testServer.PrepareResponse(400, nil, DescribeInstanceHealthBadRequest)
794+ resp, err := s.elb.DescribeInstanceHealth("testlb", "i-foooo")
795+ c.Assert(err, NotNil)
796+ c.Assert(resp, IsNil)
797+ c.Assert(err, ErrorMatches, ".*i-foooo.*(InvalidInstance).*")
798+}
799+
800+func (s *S) TestConfigureHealthCheck(c *C) {
801+ testServer.PrepareResponse(200, nil, ConfigureHealthCheck)
802+ hc := elb.HealthCheck{
803+ HealthyThreshold: 10,
804+ Interval: 30,
805+ Target: "HTTP:80/",
806+ Timeout: 5,
807+ UnhealthyThreshold: 2,
808+ }
809+ resp, err := s.elb.ConfigureHealthCheck("testlb", &hc)
810+ c.Assert(err, IsNil)
811+ values := testServer.WaitRequest().URL.Query()
812+ c.Assert(values.Get("Version"), Equals, "2012-06-01")
813+ c.Assert(values.Get("Signature"), Not(Equals), "")
814+ c.Assert(values.Get("Timestamp"), Not(Equals), "")
815+ c.Assert(values.Get("Action"), Equals, "ConfigureHealthCheck")
816+ c.Assert(values.Get("LoadBalancerName"), Equals, "testlb")
817+ c.Assert(values.Get("HealthCheck.HealthyThreshold"), Equals, "10")
818+ c.Assert(values.Get("HealthCheck.Interval"), Equals, "30")
819+ c.Assert(values.Get("HealthCheck.Target"), Equals, "HTTP:80/")
820+ c.Assert(values.Get("HealthCheck.Timeout"), Equals, "5")
821+ c.Assert(values.Get("HealthCheck.UnhealthyThreshold"), Equals, "2")
822+ c.Assert(resp.HealthCheck.HealthyThreshold, Equals, 10)
823+ c.Assert(resp.HealthCheck.Interval, Equals, 30)
824+ c.Assert(resp.HealthCheck.Target, Equals, "HTTP:80/")
825+ c.Assert(resp.HealthCheck.Timeout, Equals, 5)
826+ c.Assert(resp.HealthCheck.UnhealthyThreshold, Equals, 2)
827+}
828+
829+func (s *S) TestConfigureHealthCheckBadRequest(c *C) {
830+ testServer.PrepareResponse(400, nil, ConfigureHealthCheckBadRequest)
831+ hc := elb.HealthCheck{
832+ HealthyThreshold: 10,
833+ Interval: 30,
834+ Target: "HTTP:80/",
835+ Timeout: 5,
836+ UnhealthyThreshold: 2,
837+ }
838+ resp, err := s.elb.ConfigureHealthCheck("foolb", &hc)
839+ c.Assert(resp, IsNil)
840+ c.Assert(err, NotNil)
841+ c.Assert(err, ErrorMatches, ".*foolb.*(LoadBalancerNotFound).*")
842+}
843
844=== added file 'elb/elbi_test.go'
845--- elb/elbi_test.go 1970-01-01 00:00:00 +0000
846+++ elb/elbi_test.go 2013-03-08 18:46:22 +0000
847@@ -0,0 +1,300 @@
848+package elb_test
849+
850+import (
851+ "flag"
852+ "launchpad.net/goamz/aws"
853+ "launchpad.net/goamz/ec2"
854+ "launchpad.net/goamz/elb"
855+ . "launchpad.net/gocheck"
856+)
857+
858+var amazon = flag.Bool("amazon", false, "Enable tests against amazon server")
859+
860+// AmazonServer represents an Amazon AWS server.
861+type AmazonServer struct {
862+ auth aws.Auth
863+}
864+
865+func (s *AmazonServer) SetUp(c *C) {
866+ auth, err := aws.EnvAuth()
867+ if err != nil {
868+ c.Fatal(err)
869+ }
870+ s.auth = auth
871+}
872+
873+var _ = Suite(&AmazonClientSuite{})
874+
875+// AmazonClientSuite tests the client against a live AWS server.
876+type AmazonClientSuite struct {
877+ srv AmazonServer
878+ ClientTests
879+}
880+
881+// ClientTests defines integration tests designed to test the client.
882+// It is not used as a test suite in itself, but embedded within
883+// another type.
884+type ClientTests struct {
885+ elb *elb.ELB
886+ ec2 *ec2.EC2
887+}
888+
889+func (s *AmazonClientSuite) SetUpSuite(c *C) {
890+ if !*amazon {
891+ c.Skip("AmazonClientSuite tests not enabled")
892+ }
893+ s.srv.SetUp(c)
894+ s.elb = elb.New(s.srv.auth, aws.USEast)
895+ s.ec2 = ec2.New(s.srv.auth, aws.USEast)
896+}
897+
898+func (s *ClientTests) TestCreateAndDelete(c *C) {
899+ createLBReq := elb.Create{
900+ Name: "testlb",
901+ AvailZones: []string{"us-east-1a"},
902+ Listeners: []elb.Listener{
903+ {
904+ InstancePort: 80,
905+ InstanceProtocol: "http",
906+ LoadBalancerPort: 80,
907+ Protocol: "http",
908+ },
909+ },
910+ }
911+ resp, err := s.elb.Create(&createLBReq)
912+ c.Assert(err, IsNil)
913+ defer s.elb.Delete(createLBReq.Name)
914+ c.Assert(resp.DNSName, Not(Equals), "")
915+ deleteResp, err := s.elb.Delete(createLBReq.Name)
916+ c.Assert(err, IsNil)
917+ c.Assert(deleteResp.RequestId, Not(Equals), "")
918+}
919+
920+func (s *ClientTests) TestCreateError(c *C) {
921+ createLBReq := elb.Create{
922+ Name: "testlb",
923+ AvailZones: []string{"us-east-1a"},
924+ Subnets: []string{"subnetid-1"},
925+ Listeners: []elb.Listener{
926+ {
927+ InstancePort: 80,
928+ InstanceProtocol: "http",
929+ LoadBalancerPort: 80,
930+ Protocol: "http",
931+ },
932+ },
933+ }
934+ resp, err := s.elb.Create(&createLBReq)
935+ c.Assert(resp, IsNil)
936+ c.Assert(err, NotNil)
937+ e, ok := err.(*elb.Error)
938+ c.Assert(ok, Equals, true)
939+ c.Assert(e.Message, Matches, "Only one of .* or .* may be specified")
940+ c.Assert(e.Code, Equals, "ValidationError")
941+}
942+
943+func (s *ClientTests) createInstanceAndLB(c *C) (*elb.Create, string) {
944+ options := ec2.RunInstances{
945+ ImageId: "ami-ccf405a5",
946+ InstanceType: "t1.micro",
947+ AvailZone: "us-east-1c",
948+ }
949+ resp1, err := s.ec2.RunInstances(&options)
950+ c.Assert(err, IsNil)
951+ instId := resp1.Instances[0].InstanceId
952+ createLBReq := elb.Create{
953+ Name: "testlb",
954+ AvailZones: []string{"us-east-1c"},
955+ Listeners: []elb.Listener{
956+ {
957+ InstancePort: 80,
958+ InstanceProtocol: "http",
959+ LoadBalancerPort: 80,
960+ Protocol: "http",
961+ },
962+ },
963+ }
964+ _, err = s.elb.Create(&createLBReq)
965+ c.Assert(err, IsNil)
966+ return &createLBReq, instId
967+}
968+
969+// Cost: 0.02 USD
970+func (s *ClientTests) TestCreateRegisterAndDeregisterInstance(c *C) {
971+ createLBReq, instId := s.createInstanceAndLB(c)
972+ defer func() {
973+ _, err := s.elb.Delete(createLBReq.Name)
974+ c.Check(err, IsNil)
975+ _, err = s.ec2.TerminateInstances([]string{instId})
976+ c.Check(err, IsNil)
977+ }()
978+ resp, err := s.elb.RegisterInstances(createLBReq.Name, []string{instId})
979+ c.Assert(err, IsNil)
980+ c.Assert(resp.InstanceIds, DeepEquals, []string{instId})
981+ resp2, err := s.elb.DeregisterInstances(createLBReq.Name, []string{instId})
982+ c.Assert(err, IsNil)
983+ c.Assert(resp2, Not(Equals), "")
984+}
985+
986+func (s *ClientTests) TestDescribeLoadBalancers(c *C) {
987+ createLBReq := elb.Create{
988+ Name: "testlb",
989+ AvailZones: []string{"us-east-1a"},
990+ Listeners: []elb.Listener{
991+ {
992+ InstancePort: 80,
993+ InstanceProtocol: "http",
994+ LoadBalancerPort: 80,
995+ Protocol: "http",
996+ },
997+ },
998+ }
999+ _, err := s.elb.Create(&createLBReq)
1000+ c.Assert(err, IsNil)
1001+ defer s.elb.Delete(createLBReq.Name)
1002+ resp, err := s.elb.DescribeLoadBalancers()
1003+ c.Assert(err, IsNil)
1004+ c.Assert(len(resp.LoadBalancerDescriptions) > 0, Equals, true)
1005+ c.Assert(resp.LoadBalancerDescriptions[0].AvailZones, DeepEquals, []string{"us-east-1a"})
1006+ c.Assert(resp.LoadBalancerDescriptions[0].Name, Equals, "testlb")
1007+ c.Assert(resp.LoadBalancerDescriptions[0].Scheme, Equals, "internet-facing")
1008+ hc := elb.HealthCheck{
1009+ HealthyThreshold: 10,
1010+ Interval: 30,
1011+ Target: "TCP:80",
1012+ Timeout: 5,
1013+ UnhealthyThreshold: 2,
1014+ }
1015+ c.Assert(resp.LoadBalancerDescriptions[0].HealthCheck, DeepEquals, hc)
1016+ ld := []elb.ListenerDescription{
1017+ {
1018+ Listener: elb.Listener{
1019+ Protocol: "HTTP",
1020+ LoadBalancerPort: 80,
1021+ InstanceProtocol: "HTTP",
1022+ InstancePort: 80,
1023+ },
1024+ },
1025+ }
1026+ c.Assert(resp.LoadBalancerDescriptions[0].ListenerDescriptions, DeepEquals, ld)
1027+ ssg := elb.SourceSecurityGroup{
1028+ GroupName: "amazon-elb-sg",
1029+ OwnerAlias: "amazon-elb",
1030+ }
1031+ c.Assert(resp.LoadBalancerDescriptions[0].SourceSecurityGroup, DeepEquals, ssg)
1032+}
1033+
1034+func (s *ClientTests) TestDescribeLoadBalancersBadRequest(c *C) {
1035+ resp, err := s.elb.DescribeLoadBalancers("absentlb")
1036+ c.Assert(err, NotNil)
1037+ c.Assert(resp, IsNil)
1038+ c.Assert(err, ErrorMatches, ".*(LoadBalancerNotFound).*")
1039+}
1040+
1041+func (s *ClientTests) TestDescribeInstanceHealth(c *C) {
1042+ createLBReq, instId := s.createInstanceAndLB(c)
1043+ defer func() {
1044+ _, err := s.elb.Delete(createLBReq.Name)
1045+ c.Check(err, IsNil)
1046+ _, err = s.ec2.TerminateInstances([]string{instId})
1047+ c.Check(err, IsNil)
1048+ }()
1049+ _, err := s.elb.RegisterInstances(createLBReq.Name, []string{instId})
1050+ c.Assert(err, IsNil)
1051+ resp, err := s.elb.DescribeInstanceHealth(createLBReq.Name, instId)
1052+ c.Assert(err, IsNil)
1053+ c.Assert(len(resp.InstanceStates) > 0, Equals, true)
1054+ c.Assert(resp.InstanceStates[0].Description, Equals, "Instance is in pending state.")
1055+ c.Assert(resp.InstanceStates[0].InstanceId, Equals, instId)
1056+ c.Assert(resp.InstanceStates[0].State, Equals, "OutOfService")
1057+ c.Assert(resp.InstanceStates[0].ReasonCode, Equals, "Instance")
1058+}
1059+
1060+func (s *ClientTests) TestDescribeInstanceHealthBadRequest(c *C) {
1061+ createLBReq := elb.Create{
1062+ Name: "testlb",
1063+ AvailZones: []string{"us-east-1a"},
1064+ Listeners: []elb.Listener{
1065+ {
1066+ InstancePort: 80,
1067+ InstanceProtocol: "http",
1068+ LoadBalancerPort: 80,
1069+ Protocol: "http",
1070+ },
1071+ },
1072+ }
1073+ _, err := s.elb.Create(&createLBReq)
1074+ c.Assert(err, IsNil)
1075+ resp, err := s.elb.DescribeInstanceHealth(createLBReq.Name, "i-foo")
1076+ c.Assert(resp, IsNil)
1077+ c.Assert(err, NotNil)
1078+ c.Assert(err, ErrorMatches, ".*i-foo.*(InvalidInstance).*")
1079+}
1080+
1081+func (s *ClientTests) TestConfigureHealthCheck(c *C) {
1082+ createLBReq := elb.Create{
1083+ Name: "testlb",
1084+ AvailZones: []string{"us-east-1a"},
1085+ Listeners: []elb.Listener{
1086+ {
1087+ InstancePort: 80,
1088+ InstanceProtocol: "http",
1089+ LoadBalancerPort: 80,
1090+ Protocol: "http",
1091+ },
1092+ },
1093+ }
1094+ _, err := s.elb.Create(&createLBReq)
1095+ c.Assert(err, IsNil)
1096+ defer func() {
1097+ _, err := s.elb.Delete(createLBReq.Name)
1098+ c.Check(err, IsNil)
1099+ }()
1100+ hc := elb.HealthCheck{
1101+ HealthyThreshold: 10,
1102+ Interval: 30,
1103+ Target: "HTTP:80/",
1104+ Timeout: 5,
1105+ UnhealthyThreshold: 2,
1106+ }
1107+ resp, err := s.elb.ConfigureHealthCheck(createLBReq.Name, &hc)
1108+ c.Assert(err, IsNil)
1109+ c.Assert(resp.HealthCheck.HealthyThreshold, Equals, 10)
1110+ c.Assert(resp.HealthCheck.Interval, Equals, 30)
1111+ c.Assert(resp.HealthCheck.Target, Equals, "HTTP:80/")
1112+ c.Assert(resp.HealthCheck.Timeout, Equals, 5)
1113+ c.Assert(resp.HealthCheck.UnhealthyThreshold, Equals, 2)
1114+}
1115+
1116+func (s *ClientTests) TestConfigureHealthCheckBadRequest(c *C) {
1117+ createLBReq := elb.Create{
1118+ Name: "testlb",
1119+ AvailZones: []string{"us-east-1a"},
1120+ Listeners: []elb.Listener{
1121+ {
1122+ InstancePort: 80,
1123+ InstanceProtocol: "http",
1124+ LoadBalancerPort: 80,
1125+ Protocol: "http",
1126+ },
1127+ },
1128+ }
1129+ _, err := s.elb.Create(&createLBReq)
1130+ c.Assert(err, IsNil)
1131+ defer func() {
1132+ _, err := s.elb.Delete(createLBReq.Name)
1133+ c.Check(err, IsNil)
1134+ }()
1135+ hc := elb.HealthCheck{
1136+ HealthyThreshold: 10,
1137+ Interval: 30,
1138+ Target: "HTTP:80",
1139+ Timeout: 5,
1140+ UnhealthyThreshold: 2,
1141+ }
1142+ resp, err := s.elb.ConfigureHealthCheck(createLBReq.Name, &hc)
1143+ c.Assert(resp, IsNil)
1144+ c.Assert(err, NotNil)
1145+ expected := "HealthCheck HTTP Target must specify a port followed by a path that begins with a slash. e.g. HTTP:80/ping/this/path (ValidationError)"
1146+ c.Assert(err.Error(), Equals, expected)
1147+}
1148
1149=== added file 'elb/elbt_test.go'
1150--- elb/elbt_test.go 1970-01-01 00:00:00 +0000
1151+++ elb/elbt_test.go 2013-03-08 18:46:22 +0000
1152@@ -0,0 +1,185 @@
1153+package elb_test
1154+
1155+import (
1156+ "launchpad.net/goamz/aws"
1157+ "launchpad.net/goamz/elb"
1158+ "launchpad.net/goamz/elb/elbtest"
1159+ . "launchpad.net/gocheck"
1160+)
1161+
1162+// LocalServer represents a local elbtest fake server.
1163+type LocalServer struct {
1164+ auth aws.Auth
1165+ region aws.Region
1166+ srv *elbtest.Server
1167+}
1168+
1169+func (s *LocalServer) SetUp(c *C) {
1170+ srv, err := elbtest.NewServer()
1171+ c.Assert(err, IsNil)
1172+ c.Assert(srv, NotNil)
1173+ s.srv = srv
1174+ s.region = aws.Region{ELBEndpoint: srv.URL()}
1175+}
1176+
1177+// LocalServerSuite defines tests that will run
1178+// against the local elbtest server. It includes
1179+// selected tests from ClientTests;
1180+// when the elbtest functionality is sufficient, it should
1181+// include all of them, and ClientTests can be simply embedded.
1182+type LocalServerSuite struct {
1183+ srv LocalServer
1184+ ServerTests
1185+ clientTests ClientTests
1186+}
1187+
1188+// ServerTests defines a set of tests designed to test
1189+// the elbtest local fake elb server.
1190+// It is not used as a test suite in itself, but embedded within
1191+// another type.
1192+type ServerTests struct {
1193+ elb *elb.ELB
1194+}
1195+
1196+// AmazonServerSuite runs the elbtest server tests against a live ELB server.
1197+// It will only be activated if the -all flag is specified.
1198+type AmazonServerSuite struct {
1199+ srv AmazonServer
1200+ ServerTests
1201+}
1202+
1203+var _ = Suite(&AmazonServerSuite{})
1204+
1205+func (s *AmazonServerSuite) SetUpSuite(c *C) {
1206+ if !*amazon {
1207+ c.Skip("AmazonServerSuite tests not enabled")
1208+ }
1209+ s.srv.SetUp(c)
1210+ s.ServerTests.elb = elb.New(s.srv.auth, aws.USEast)
1211+}
1212+
1213+var _ = Suite(&LocalServerSuite{})
1214+
1215+func (s *LocalServerSuite) SetUpSuite(c *C) {
1216+ s.srv.SetUp(c)
1217+ s.ServerTests.elb = elb.New(s.srv.auth, s.srv.region)
1218+ s.clientTests.elb = elb.New(s.srv.auth, s.srv.region)
1219+}
1220+
1221+func (s *LocalServerSuite) TestCreate(c *C) {
1222+ s.clientTests.TestCreateAndDelete(c)
1223+}
1224+
1225+func (s *LocalServerSuite) TestCreateError(c *C) {
1226+ s.clientTests.TestCreateError(c)
1227+}
1228+
1229+func (s *LocalServerSuite) TestDescribeLoadBalancer(c *C) {
1230+ s.clientTests.TestDescribeLoadBalancers(c)
1231+}
1232+
1233+func (s *LocalServerSuite) TestDescribeLoadBalancerListsAddedByNewLoadbalancerFunc(c *C) {
1234+ srv := s.srv.srv
1235+ srv.New("somelb")
1236+ defer srv.Remove("somelb")
1237+ resp, err := s.clientTests.elb.DescribeLoadBalancers()
1238+ c.Assert(err, IsNil)
1239+ isPresent := false
1240+ for _, desc := range resp.LoadBalancerDescriptions {
1241+ if desc.Name == "somelb" {
1242+ isPresent = true
1243+ }
1244+ }
1245+ c.Assert(isPresent, Equals, true)
1246+}
1247+
1248+func (s *LocalServerSuite) TestDescribeLoadBalancersBadRequest(c *C) {
1249+ s.clientTests.TestDescribeLoadBalancersBadRequest(c)
1250+}
1251+
1252+func (s *LocalServerSuite) TestRegisterInstances(c *C) {
1253+ srv := s.srv.srv
1254+ instId := srv.NewInstance()
1255+ defer srv.RemoveInstance(instId)
1256+ srv.New("testlb")
1257+ defer srv.Remove("testlb")
1258+ resp, err := s.clientTests.elb.RegisterInstances("testlb", []string{instId})
1259+ c.Assert(err, IsNil)
1260+ c.Assert(resp.InstanceIds, DeepEquals, []string{instId})
1261+}
1262+
1263+func (s *LocalServerSuite) TestRegisterInstanceWithAbsentInstance(c *C) {
1264+ srv := s.srv.srv
1265+ srv.New("testlb")
1266+ defer srv.Remove("testlb")
1267+ resp, err := s.clientTests.elb.RegisterInstances("testlb", []string{"i-212"})
1268+ c.Assert(err, NotNil)
1269+ c.Assert(err, ErrorMatches, `^InvalidInstance found in \[i-212\]. Invalid id: "i-212" \(InvalidInstance\)$`)
1270+ c.Assert(resp, IsNil)
1271+}
1272+
1273+func (s *LocalServerSuite) TestRegisterInstanceWithAbsentLoadBalancer(c *C) {
1274+ // the verification if the lb exists is done before the instances, so there is no need to create
1275+ // fixture instances for this test, it'll never get that far
1276+ resp, err := s.clientTests.elb.RegisterInstances("absentlb", []string{"i-212"})
1277+ c.Assert(err, NotNil)
1278+ c.Assert(err, ErrorMatches, `^There is no ACTIVE Load Balancer named 'absentlb' \(LoadBalancerNotFound\)$`)
1279+ c.Assert(resp, IsNil)
1280+}
1281+
1282+func (s *LocalServerSuite) TestDeregisterInstance(c *C) {
1283+ // there is no need to register the instance first, amazon returns the same response
1284+ // in both cases (instance registered or not)
1285+ srv := s.srv.srv
1286+ instId := srv.NewInstance()
1287+ defer srv.RemoveInstance(instId)
1288+ srv.New("testlb")
1289+ defer srv.Remove("testlb")
1290+ resp, err := s.clientTests.elb.DeregisterInstances("testlb", []string{instId})
1291+ c.Assert(err, IsNil)
1292+ c.Assert(resp.RequestId, Not(Equals), "")
1293+}
1294+
1295+func (s *LocalServerSuite) TestDeregisterInstanceWithAbsentLoadBalancer(c *C) {
1296+ resp, err := s.clientTests.elb.DeregisterInstances("absentlb", []string{"i-212"})
1297+ c.Assert(resp, IsNil)
1298+ c.Assert(err, NotNil)
1299+ c.Assert(err, ErrorMatches, `^There is no ACTIVE Load Balancer named 'absentlb' \(LoadBalancerNotFound\)$`)
1300+}
1301+
1302+func (s *LocalServerSuite) TestDeregisterInstanceWithAbsentInstance(c *C) {
1303+ srv := s.srv.srv
1304+ srv.New("testlb")
1305+ defer srv.Remove("testlb")
1306+ resp, err := s.clientTests.elb.DeregisterInstances("testlb", []string{"i-212"})
1307+ c.Assert(resp, IsNil)
1308+ c.Assert(err, NotNil)
1309+ c.Assert(err, ErrorMatches, `^InvalidInstance found in \[i-212\]. Invalid id: "i-212" \(InvalidInstance\)$`)
1310+}
1311+
1312+func (s *LocalServerSuite) TestDescribeInstanceHealth(c *C) {
1313+ srv := s.srv.srv
1314+ instId := srv.NewInstance()
1315+ defer srv.RemoveInstance(instId)
1316+ srv.New("testlb")
1317+ defer srv.Remove("testlb")
1318+ resp, err := s.clientTests.elb.DescribeInstanceHealth("testlb", instId)
1319+ c.Assert(err, IsNil)
1320+ c.Assert(len(resp.InstanceStates) > 0, Equals, true)
1321+ c.Assert(resp.InstanceStates[0].Description, Equals, "Instance is in pending state.")
1322+ c.Assert(resp.InstanceStates[0].InstanceId, Equals, instId)
1323+ c.Assert(resp.InstanceStates[0].State, Equals, "OutOfService")
1324+ c.Assert(resp.InstanceStates[0].ReasonCode, Equals, "Instance")
1325+}
1326+
1327+func (s *LocalServerSuite) TestDescribeInstanceHealthBadRequest(c *C) {
1328+ s.clientTests.TestDescribeInstanceHealthBadRequest(c)
1329+}
1330+
1331+func (s *LocalServerSuite) TestConfigureHealthCheck(c *C) {
1332+ s.clientTests.TestConfigureHealthCheck(c)
1333+}
1334+
1335+func (s *LocalServerSuite) TestConfigureHealthCheckBadRequest(c *C) {
1336+ s.clientTests.TestConfigureHealthCheckBadRequest(c)
1337+}
1338
1339=== added directory 'elb/elbtest'
1340=== added file 'elb/elbtest/server.go'
1341--- elb/elbtest/server.go 1970-01-01 00:00:00 +0000
1342+++ elb/elbtest/server.go 2013-03-08 18:46:22 +0000
1343@@ -0,0 +1,455 @@
1344+// Package elbtest implements a fake ELB provider with the capability of
1345+// inducing errors on any given operation, and retrospectively determining what
1346+// operations have been carried out.
1347+package elbtest
1348+
1349+import (
1350+ "encoding/xml"
1351+ "fmt"
1352+ "launchpad.net/goamz/elb"
1353+ "net"
1354+ "net/http"
1355+ "net/url"
1356+ "regexp"
1357+ "strconv"
1358+ "strings"
1359+ "sync"
1360+)
1361+
1362+// Server implements an ELB simulator for use in testing.
1363+type Server struct {
1364+ url string
1365+ listener net.Listener
1366+ mutex sync.Mutex
1367+ reqId int
1368+ lbs map[string]string
1369+ lbsReqs map[string]url.Values
1370+ instances []string
1371+ instCount int
1372+}
1373+
1374+// NewServer starts and returns a new server
1375+func NewServer() (*Server, error) {
1376+ l, err := net.Listen("tcp", "localhost:0")
1377+ if err != nil {
1378+ return nil, fmt.Errorf("cannot listen on localhost: %v", err)
1379+ }
1380+ srv := &Server{
1381+ listener: l,
1382+ url: "http://" + l.Addr().String(),
1383+ lbsReqs: map[string]url.Values{},
1384+ lbs: make(map[string]string),
1385+ }
1386+ go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
1387+ srv.serveHTTP(w, req)
1388+ }))
1389+ return srv, nil
1390+}
1391+
1392+// Quit closes down the server.
1393+func (srv *Server) Quit() {
1394+ srv.listener.Close()
1395+}
1396+
1397+// URL returns the URL of the server.
1398+func (srv *Server) URL() string {
1399+ return srv.url
1400+}
1401+
1402+type xmlErrors struct {
1403+ XMLName string `xml:"ErrorResponse"`
1404+ Error elb.Error
1405+}
1406+
1407+func (srv *Server) error(w http.ResponseWriter, err *elb.Error) {
1408+ w.WriteHeader(err.StatusCode)
1409+ xmlErr := xmlErrors{Error: *err}
1410+ if e := xml.NewEncoder(w).Encode(xmlErr); e != nil {
1411+ panic(e)
1412+ }
1413+}
1414+
1415+func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
1416+ req.ParseForm()
1417+ srv.mutex.Lock()
1418+ defer srv.mutex.Unlock()
1419+ f := actions[req.Form.Get("Action")]
1420+ if f == nil {
1421+ srv.error(w, &elb.Error{
1422+ StatusCode: 400,
1423+ Code: "InvalidParameterValue",
1424+ Message: "Unrecognized Action",
1425+ })
1426+ }
1427+ reqId := fmt.Sprintf("req%0X", srv.reqId)
1428+ srv.reqId++
1429+ if resp, err := f(srv, w, req, reqId); err == nil {
1430+ if err := xml.NewEncoder(w).Encode(resp); err != nil {
1431+ panic(err)
1432+ }
1433+ } else {
1434+ switch err.(type) {
1435+ case *elb.Error:
1436+ srv.error(w, err.(*elb.Error))
1437+ default:
1438+ panic(err)
1439+ }
1440+ }
1441+}
1442+
1443+func (srv *Server) create(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
1444+ composition := map[string]string{
1445+ "AvailabilityZones.member.1": "Subnets.member.1",
1446+ }
1447+ if err := srv.validateComposition(req, composition); err != nil {
1448+ return nil, err
1449+ }
1450+ required := []string{
1451+ "Listeners.member.1.InstancePort",
1452+ "Listeners.member.1.InstanceProtocol",
1453+ "Listeners.member.1.Protocol",
1454+ "Listeners.member.1.LoadBalancerPort",
1455+ "LoadBalancerName",
1456+ }
1457+ if err := srv.validate(req, required); err != nil {
1458+ return nil, err
1459+ }
1460+ path := req.FormValue("Path")
1461+ if path == "" {
1462+ path = "/"
1463+ }
1464+ lbName := req.FormValue("LoadBalancerName")
1465+ srv.lbsReqs[lbName] = req.Form
1466+ srv.lbs[lbName] = fmt.Sprintf("%s-some-aws-stuff.us-east-1.elb.amazonaws.com", lbName)
1467+ return elb.CreateResp{
1468+ DNSName: srv.lbs[lbName],
1469+ }, nil
1470+}
1471+
1472+func (srv *Server) delete(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
1473+ if err := srv.validate(req, []string{"LoadBalancerName"}); err != nil {
1474+ return nil, err
1475+ }
1476+ srv.Remove(req.FormValue("LoadBalancerName"))
1477+ return elb.SimpleResp{RequestId: reqId}, nil
1478+}
1479+
1480+func (srv *Server) registerInstances(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
1481+ required := []string{"LoadBalancerName", "Instances.member.1.InstanceId"}
1482+ if err := srv.validate(req, required); err != nil {
1483+ return nil, err
1484+ }
1485+ if err := srv.lbExists(req.FormValue("LoadBalancerName")); err != nil {
1486+ return nil, err
1487+ }
1488+ instIds := []string{}
1489+ i := 1
1490+ instId := req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
1491+ for instId != "" {
1492+ if err := srv.instanceExists(instId); err != nil {
1493+ return nil, err
1494+ }
1495+ instIds = append(instIds, instId)
1496+ i++
1497+ instId = req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
1498+ }
1499+ return elb.RegisterInstancesResp{InstanceIds: instIds}, nil
1500+}
1501+
1502+func (srv *Server) deregisterInstances(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
1503+ required := []string{"LoadBalancerName"}
1504+ if err := srv.validate(req, required); err != nil {
1505+ return nil, err
1506+ }
1507+ if err := srv.lbExists(req.FormValue("LoadBalancerName")); err != nil {
1508+ return nil, err
1509+ }
1510+ i := 1
1511+ instId := req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
1512+ for instId != "" {
1513+ if err := srv.instanceExists(instId); err != nil {
1514+ return nil, err
1515+ }
1516+ i++
1517+ instId = req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
1518+ }
1519+ return elb.SimpleResp{RequestId: reqId}, nil
1520+}
1521+
1522+func (srv *Server) describeLoadBalancers(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
1523+ i := 1
1524+ lbName := req.FormValue(fmt.Sprintf("LoadBalancerNames.member.%d", i))
1525+ for lbName != "" {
1526+ key := fmt.Sprintf("LoadBalancerNames.member.%d", i)
1527+ if req.FormValue(key) != "" {
1528+ if err := srv.lbExists(req.FormValue(key)); err != nil {
1529+ return nil, err
1530+ }
1531+ }
1532+ i++
1533+ lbName = req.FormValue(fmt.Sprintf("LoadBalancerNames.member.%d", i))
1534+ }
1535+ var resp elb.DescribeResp
1536+ for name, value := range srv.lbsReqs {
1537+ lds := []elb.ListenerDescription{}
1538+ i := 1
1539+ protocol := value.Get(fmt.Sprintf("Listeners.member.%d.Protocol", i))
1540+ for protocol != "" {
1541+ key := fmt.Sprintf("Listeners.member.%d.", i)
1542+ lInstPort, _ := strconv.Atoi(value.Get(key + "InstancePort"))
1543+ lLBPort, _ := strconv.Atoi(value.Get(key + "LoadBalancerPort"))
1544+ lDescription := elb.ListenerDescription{
1545+ Listener: elb.Listener{
1546+ Protocol: strings.ToUpper(protocol),
1547+ InstanceProtocol: strings.ToUpper(value.Get(key + "InstanceProtocol")),
1548+ LoadBalancerPort: lLBPort,
1549+ InstancePort: lInstPort,
1550+ },
1551+ }
1552+ i++
1553+ protocol = value.Get(fmt.Sprintf("Listeners.member.%d.Protocol", i))
1554+ lds = append(lds, lDescription)
1555+ }
1556+ sourceSecGroup := srv.makeSourceSecGroup(&value)
1557+ lbDesc := elb.LoadBalancerDescription{
1558+ AvailZones: []string{value.Get("AvailabilityZones.member.1")},
1559+ Name: name,
1560+ HealthCheck: srv.makeHealthCheck(&value),
1561+ ListenerDescriptions: lds,
1562+ SourceSecurityGroup: sourceSecGroup,
1563+ }
1564+ if value.Get("Scheme") == "" {
1565+ lbDesc.Scheme = "internet-facing"
1566+ }
1567+ lbDesc.Name = value.Get("LoadBalancerName")
1568+ lbDesc.DNSName = srv.lbs[lbDesc.Name]
1569+ resp.LoadBalancerDescriptions = append(resp.LoadBalancerDescriptions, lbDesc)
1570+ }
1571+ for name, _ := range srv.lbs {
1572+ desc := elb.LoadBalancerDescription{
1573+ AvailZones: []string{"us-east-1a"},
1574+ Name: name,
1575+ HealthCheck: srv.makeHealthCheck(&url.Values{}),
1576+ }
1577+ resp.LoadBalancerDescriptions = append(resp.LoadBalancerDescriptions, desc)
1578+ }
1579+ return resp, nil
1580+}
1581+
1582+func (srv *Server) makeHealthCheck(value *url.Values) elb.HealthCheck {
1583+ ht := 10
1584+ timeout := 5
1585+ ut := 2
1586+ interval := 30
1587+ target := "TCP:80"
1588+ if v := value.Get("HealthCheck.HealthyThreshold"); v != "" {
1589+ ht, _ = strconv.Atoi(v)
1590+ }
1591+ if v := value.Get("HealthCheck.Timeout"); v != "" {
1592+ timeout, _ = strconv.Atoi(v)
1593+ }
1594+ if v := value.Get("HealthCheck.UnhealthyThreshold"); v != "" {
1595+ ut, _ = strconv.Atoi(v)
1596+ }
1597+ if v := value.Get("HealthCheck.Interval"); v != "" {
1598+ interval, _ = strconv.Atoi(v)
1599+ }
1600+ if v := value.Get("HealthCheck.Target"); v != "" {
1601+ target = v
1602+ }
1603+ return elb.HealthCheck{
1604+ HealthyThreshold: ht,
1605+ Interval: interval,
1606+ Target: target,
1607+ Timeout: timeout,
1608+ UnhealthyThreshold: ut,
1609+ }
1610+}
1611+
1612+func (srv *Server) makeSourceSecGroup(value *url.Values) elb.SourceSecurityGroup {
1613+ name := "amazon-elb-sg"
1614+ alias := "amazon-elb"
1615+ if v := value.Get("SourceSecurityGroup.GroupName"); v != "" {
1616+ name = v
1617+ }
1618+ if v := value.Get("SourceSecurityGroup.OwnerAlias"); v != "" {
1619+ alias = v
1620+ }
1621+ return elb.SourceSecurityGroup{
1622+ GroupName: name,
1623+ OwnerAlias: alias,
1624+ }
1625+}
1626+
1627+func (srv *Server) describeInstanceHealth(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
1628+ if err := srv.validate(req, []string{"LoadBalancerName"}); err != nil {
1629+ return nil, err
1630+ }
1631+ if err := srv.lbExists(req.FormValue("LoadBalancerName")); err != nil {
1632+ return nil, err
1633+ }
1634+ resp := elb.DescribeInstanceHealthResp{
1635+ InstanceStates: []elb.InstanceState{},
1636+ }
1637+ i := 1
1638+ instanceId := req.FormValue("Instances.member.1.InstanceId")
1639+ for instanceId != "" {
1640+ if err := srv.instanceExists(instanceId); err != nil {
1641+ return nil, err
1642+ }
1643+ is := elb.InstanceState{
1644+ Description: "Instance is in pending state.",
1645+ InstanceId: instanceId,
1646+ State: "OutOfService",
1647+ ReasonCode: "Instance",
1648+ }
1649+ resp.InstanceStates = append(resp.InstanceStates, is)
1650+ i++
1651+ instanceId = req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
1652+ }
1653+ return resp, nil
1654+}
1655+
1656+func (srv *Server) configureHealthCheck(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
1657+ required := []string{
1658+ "LoadBalancerName",
1659+ "HealthCheck.HealthyThreshold",
1660+ "HealthCheck.Interval",
1661+ "HealthCheck.Target",
1662+ "HealthCheck.Timeout",
1663+ "HealthCheck.UnhealthyThreshold",
1664+ }
1665+ if err := srv.validate(req, required); err != nil {
1666+ return nil, err
1667+ }
1668+ target := req.FormValue("HealthCheck.Target")
1669+ r, err := regexp.Compile(`[\w]+:[\d]+\/+`)
1670+ if err != nil {
1671+ panic(err)
1672+ }
1673+ if m := r.FindStringSubmatch(target); m == nil {
1674+ return nil, &elb.Error{
1675+ StatusCode: 400,
1676+ Code: "ValidationError",
1677+ Message: "HealthCheck HTTP Target must specify a port followed by a path that begins with a slash. e.g. HTTP:80/ping/this/path",
1678+ }
1679+ }
1680+ ht, _ := strconv.Atoi(req.FormValue("HealthCheck.HealthyThreshold"))
1681+ interval, _ := strconv.Atoi(req.FormValue("HealthCheck.Interval"))
1682+ timeout, _ := strconv.Atoi(req.FormValue("HealthCheck.Timeout"))
1683+ ut, _ := strconv.Atoi(req.FormValue("HealthCheck.UnhealthyThreshold"))
1684+ return elb.HealthCheckResp{
1685+ HealthCheck: &elb.HealthCheck{
1686+ HealthyThreshold: ht,
1687+ Interval: interval,
1688+ Target: target,
1689+ Timeout: timeout,
1690+ UnhealthyThreshold: ut,
1691+ },
1692+ }, nil
1693+}
1694+
1695+func (srv *Server) instanceExists(id string) error {
1696+ for _, instId := range srv.instances {
1697+ if instId == id {
1698+ return nil
1699+ }
1700+ }
1701+ return &elb.Error{
1702+ StatusCode: 400,
1703+ Code: "InvalidInstance",
1704+ Message: fmt.Sprintf("InvalidInstance found in [%s]. Invalid id: \"%s\"", id, id),
1705+ }
1706+}
1707+
1708+func (srv *Server) lbExists(name string) error {
1709+ if _, ok := srv.lbs[name]; !ok {
1710+ return &elb.Error{
1711+ StatusCode: 400,
1712+ Code: "LoadBalancerNotFound",
1713+ Message: fmt.Sprintf("There is no ACTIVE Load Balancer named '%s'", name),
1714+ }
1715+ }
1716+ return nil
1717+}
1718+
1719+func (srv *Server) validate(req *http.Request, required []string) error {
1720+ for _, field := range required {
1721+ if req.FormValue(field) == "" {
1722+ return &elb.Error{
1723+ StatusCode: 400,
1724+ Code: "ValidationError",
1725+ Message: fmt.Sprintf("%s is required.", field),
1726+ }
1727+ }
1728+ }
1729+ return nil
1730+}
1731+
1732+// Validates the composition of the fields.
1733+//
1734+// Some fields cannot be together in the same request, such as AvailabilityZones and Subnets.
1735+// A sample map with the above requirement would be
1736+// c := map[string]string{
1737+// "AvailabilityZones.member.1": "Subnets.member.1",
1738+// }
1739+//
1740+// The server also requires that at least one of those fields are specified.
1741+func (srv *Server) validateComposition(req *http.Request, composition map[string]string) error {
1742+ for k, v := range composition {
1743+ if req.FormValue(k) != "" && req.FormValue(v) != "" {
1744+ return &elb.Error{
1745+ StatusCode: 400,
1746+ Code: "ValidationError",
1747+ Message: fmt.Sprintf("Only one of %s or %s may be specified", k, v),
1748+ }
1749+ }
1750+ if req.FormValue(k) == "" && req.FormValue(v) == "" {
1751+ return &elb.Error{
1752+ StatusCode: 400,
1753+ Code: "ValidationError",
1754+ Message: fmt.Sprintf("Either %s or %s must be specified", k, v),
1755+ }
1756+ }
1757+ }
1758+ return nil
1759+}
1760+
1761+// Creates a fake instance in the server
1762+func (srv *Server) NewInstance() string {
1763+ srv.instCount++
1764+ instId := fmt.Sprintf("i-%d", srv.instCount)
1765+ srv.instances = append(srv.instances, instId)
1766+ return instId
1767+}
1768+
1769+// Removes a fake instance from the server
1770+//
1771+// If no instance is found it does nothing
1772+func (srv *Server) RemoveInstance(instId string) {
1773+ for i, id := range srv.instances {
1774+ if id == instId {
1775+ srv.instances[i], srv.instances = srv.instances[len(srv.instances)-1], srv.instances[:len(srv.instances)-1]
1776+ }
1777+ }
1778+}
1779+
1780+// Creates a fake load balancer in the fake server
1781+func (srv *Server) New(name string) {
1782+ srv.lbs[name] = fmt.Sprintf("%s-some-aws-stuff.sa-east-1.amazonaws.com", name)
1783+}
1784+
1785+// Removes a fake load balancer from the fake server
1786+func (srv *Server) Remove(name string) {
1787+ delete(srv.lbs, name)
1788+}
1789+
1790+var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) (interface{}, error){
1791+ "CreateLoadBalancer": (*Server).create,
1792+ "DeleteLoadBalancer": (*Server).delete,
1793+ "RegisterInstancesWithLoadBalancer": (*Server).registerInstances,
1794+ "DeregisterInstancesFromLoadBalancer": (*Server).deregisterInstances,
1795+ "DescribeLoadBalancers": (*Server).describeLoadBalancers,
1796+ "DescribeInstanceHealth": (*Server).describeInstanceHealth,
1797+ "ConfigureHealthCheck": (*Server).configureHealthCheck,
1798+}
1799
1800=== added file 'elb/export_test.go'
1801--- elb/export_test.go 1970-01-01 00:00:00 +0000
1802+++ elb/export_test.go 2013-03-08 18:46:22 +0000
1803@@ -0,0 +1,9 @@
1804+package elb
1805+
1806+import (
1807+ "launchpad.net/goamz/aws"
1808+)
1809+
1810+func Sign(auth aws.Auth, method, path string, params map[string]string, host string) {
1811+ sign(auth, method, path, params, host)
1812+}
1813
1814=== added file 'elb/response_test.go'
1815--- elb/response_test.go 1970-01-01 00:00:00 +0000
1816+++ elb/response_test.go 2013-03-08 18:46:22 +0000
1817@@ -0,0 +1,205 @@
1818+package elb_test
1819+
1820+var Create = `
1821+<CreateLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1822+ <CreateLoadBalancerResult>
1823+ <DNSName>testlb-339187009.us-east-1.elb.amazonaws.com</DNSName>
1824+ </CreateLoadBalancerResult>
1825+ <ResponseMetadata>
1826+ <RequestId>0c3a8e29-490e-11e2-8647-e14ad5151f1f</RequestId>
1827+ </ResponseMetadata>
1828+</CreateLoadBalancerResponse>
1829+`
1830+
1831+var CreateBalancerBadRequest = `
1832+<ErrorResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1833+ <Error>
1834+ <Type>Sender</Type>
1835+ <Code>ValidationError</Code>
1836+ <Message>Only one of SubnetIds or AvailabilityZones may be specified</Message>
1837+ </Error>
1838+ <RequestId>159253fc-49dc-11e2-a47d-cde463c91a3c</RequestId>
1839+</ErrorResponse>
1840+`
1841+
1842+var Delete = `
1843+<DeleteLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1844+ <DeleteLoadBalancerResult/>
1845+ <ResponseMetadata>
1846+ <RequestId>8d7223db-49d7-11e2-bba9-35ba56032fe1</RequestId>
1847+ </ResponseMetadata>
1848+</DeleteLoadBalancerResponse>
1849+`
1850+
1851+var RegisterInstances = `
1852+<RegisterInstancesWithLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1853+ <RegisterInstancesWithLoadBalancerResult>
1854+ <Instances>
1855+ <member>
1856+ <InstanceId>i-b44db8ca</InstanceId>
1857+ </member>
1858+ <member>
1859+ <InstanceId>i-461ecf38</InstanceId>
1860+ </member>
1861+ </Instances>
1862+ </RegisterInstancesWithLoadBalancerResult>
1863+ <ResponseMetadata>
1864+ <RequestId>0fc82478-49e1-11e2-b947-8768f15220aa</RequestId>
1865+ </ResponseMetadata>
1866+</RegisterInstancesWithLoadBalancerResponse>
1867+`
1868+
1869+var RegisterInstancesBadRequest = `
1870+<ErrorResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1871+ <Error>
1872+ <Type>Sender</Type>
1873+ <Code>LoadBalancerNotFound</Code>
1874+ <Message>There is no ACTIVE Load Balancer named 'absentLB'</Message>
1875+ </Error>
1876+ <RequestId>19a0bb97-49f7-11e2-90b4-6bb9ec8331bf</RequestId>
1877+</ErrorResponse>
1878+`
1879+
1880+var DeregisterInstances = `
1881+<DeregisterInstancesFromLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1882+ <DeregisterInstancesFromLoadBalancerResult>
1883+ <Instances/>
1884+ </DeregisterInstancesFromLoadBalancerResult>
1885+ <ResponseMetadata>
1886+ <RequestId>d6490837-49fd-11e2-bba9-35ba56032fe1</RequestId>
1887+ </ResponseMetadata>
1888+</DeregisterInstancesFromLoadBalancerResponse>
1889+`
1890+
1891+var DeregisterInstancesBadRequest = `
1892+<ErrorResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1893+ <Error>
1894+ <Type>Sender</Type>
1895+ <Code>LoadBalancerNotFound</Code>
1896+ <Message>There is no ACTIVE Load Balancer named 'absentlb'</Message>
1897+ </Error>
1898+ <RequestId>498e2b4a-4aa1-11e2-8839-d19a879f2eec</RequestId>
1899+</ErrorResponse>
1900+`
1901+
1902+var DescribeLoadBalancers = `
1903+<DescribeLoadBalancersResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1904+ <DescribeLoadBalancersResult>
1905+ <LoadBalancerDescriptions>
1906+ <member>
1907+ <SecurityGroups/>
1908+ <CreatedTime>2012-12-27T11:51:52.970Z</CreatedTime>
1909+ <LoadBalancerName>testlb</LoadBalancerName>
1910+ <HealthCheck>
1911+ <Interval>30</Interval>
1912+ <Target>TCP:80</Target>
1913+ <HealthyThreshold>10</HealthyThreshold>
1914+ <Timeout>5</Timeout>
1915+ <UnhealthyThreshold>2</UnhealthyThreshold>
1916+ </HealthCheck>
1917+ <ListenerDescriptions>
1918+ <member>
1919+ <PolicyNames/>
1920+ <Listener>
1921+ <Protocol>HTTP</Protocol>
1922+ <LoadBalancerPort>80</LoadBalancerPort>
1923+ <InstanceProtocol>HTTP</InstanceProtocol>
1924+ <InstancePort>80</InstancePort>
1925+ </Listener>
1926+ </member>
1927+ </ListenerDescriptions>
1928+ <Instances/>
1929+ <Policies>
1930+ <AppCookieStickinessPolicies/>
1931+ <OtherPolicies/>
1932+ <LBCookieStickinessPolicies/>
1933+ </Policies>
1934+ <AvailabilityZones>
1935+ <member>us-east-1a</member>
1936+ </AvailabilityZones>
1937+ <CanonicalHostedZoneName>testlb-2087227216.us-east-1.elb.amazonaws.com</CanonicalHostedZoneName>
1938+ <CanonicalHostedZoneNameID>Z3DZXE0Q79N41H</CanonicalHostedZoneNameID>
1939+ <Scheme>internet-facing</Scheme>
1940+ <SourceSecurityGroup>
1941+ <OwnerAlias>amazon-elb</OwnerAlias>
1942+ <GroupName>amazon-elb-sg</GroupName>
1943+ </SourceSecurityGroup>
1944+ <DNSName>testlb-2087227216.us-east-1.elb.amazonaws.com</DNSName>
1945+ <BackendServerDescriptions/>
1946+ <Subnets/>
1947+ </member>
1948+ </LoadBalancerDescriptions>
1949+ </DescribeLoadBalancersResult>
1950+ <ResponseMetadata>
1951+ <RequestId>e2e81963-5055-11e2-99c7-434205631d9b</RequestId>
1952+ </ResponseMetadata>
1953+</DescribeLoadBalancersResponse>
1954+`
1955+
1956+var DescribeLoadBalancersBadRequest = `
1957+<ErrorResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1958+ <Error>
1959+ <Type>Sender</Type>
1960+ <Code>LoadBalancerNotFound</Code>
1961+ <Message>Cannot find Load Balancer absentlb</Message>
1962+ </Error>
1963+ <RequestId>f14f348e-50f7-11e2-9831-f770dd71c209</RequestId>
1964+</ErrorResponse>
1965+`
1966+
1967+var DescribeInstanceHealth = `
1968+<DescribeInstanceHealthResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1969+ <DescribeInstanceHealthResult>
1970+ <InstanceStates>
1971+ <member>
1972+ <Description>Instance registration is still in progress.</Description>
1973+ <InstanceId>i-b44db8ca</InstanceId>
1974+ <State>OutOfService</State>
1975+ <ReasonCode>ELB</ReasonCode>
1976+ </member>
1977+ </InstanceStates>
1978+ </DescribeInstanceHealthResult>
1979+ <ResponseMetadata>
1980+ <RequestId>da0d0f9e-5669-11e2-9f81-319facce7423</RequestId>
1981+ </ResponseMetadata>
1982+</DescribeInstanceHealthResponse>
1983+`
1984+
1985+var DescribeInstanceHealthBadRequest = `
1986+<ErrorResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1987+ <Error>
1988+ <Type>Sender</Type>
1989+ <Code>InvalidInstance</Code>
1990+ <Message>Could not find EC2 instance i-foooo.</Message>
1991+ </Error>
1992+ <RequestId>352e00d6-566c-11e2-a46d-313272bbb522</RequestId>
1993+</ErrorResponse>
1994+`
1995+
1996+var ConfigureHealthCheck = `
1997+<ConfigureHealthCheckResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
1998+ <ConfigureHealthCheckResult>
1999+ <HealthCheck>
2000+ <Interval>30</Interval>
2001+ <Target>HTTP:80/</Target>
2002+ <HealthyThreshold>10</HealthyThreshold>
2003+ <Timeout>5</Timeout>
2004+ <UnhealthyThreshold>2</UnhealthyThreshold>
2005+ </HealthCheck>
2006+ </ConfigureHealthCheckResult>
2007+ <ResponseMetadata>
2008+ <RequestId>a882d12c-5694-11e2-b647-594652c9487c</RequestId>
2009+ </ResponseMetadata>
2010+</ConfigureHealthCheckResponse>
2011+`
2012+
2013+var ConfigureHealthCheckBadRequest = `
2014+<ErrorResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
2015+ <Error>
2016+ <Type>Sender</Type>
2017+ <Code>LoadBalancerNotFound</Code>
2018+ <Message>There is no ACTIVE Load Balancer named 'foolb'</Message>
2019+ </Error>
2020+ <RequestId>2d9fe4a5-5697-11e2-9415-e325c02171d7</RequestId>
2021+</ErrorResponse>
2022+`
2023
2024=== added file 'elb/sign.go'
2025--- elb/sign.go 1970-01-01 00:00:00 +0000
2026+++ elb/sign.go 2013-03-08 18:46:22 +0000
2027@@ -0,0 +1,35 @@
2028+package elb
2029+
2030+import (
2031+ "crypto/hmac"
2032+ "crypto/sha256"
2033+ "encoding/base64"
2034+ "launchpad.net/goamz/aws"
2035+ "sort"
2036+ "strings"
2037+)
2038+
2039+var b64 = base64.StdEncoding
2040+
2041+func sign(auth aws.Auth, method, path string, params map[string]string, host string) {
2042+ params["AWSAccessKeyId"] = auth.AccessKey
2043+ params["SignatureVersion"] = "2"
2044+ params["SignatureMethod"] = "HmacSHA256"
2045+
2046+ var keys, sarray []string
2047+ for k, _ := range params {
2048+ keys = append(keys, k)
2049+ }
2050+ sort.Strings(keys)
2051+ for _, k := range keys {
2052+ sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(params[k]))
2053+ }
2054+ joined := strings.Join(sarray, "&")
2055+ payload := method + "\n" + host + "\n" + path + "\n" + joined
2056+ hash := hmac.New(sha256.New, []byte(auth.SecretKey))
2057+ hash.Write([]byte(payload))
2058+ signature := make([]byte, b64.EncodedLen(hash.Size()))
2059+ b64.Encode(signature, hash.Sum(nil))
2060+
2061+ params["Signature"] = string(signature)
2062+}
2063
2064=== added file 'elb/sign_test.go'
2065--- elb/sign_test.go 1970-01-01 00:00:00 +0000
2066+++ elb/sign_test.go 2013-03-08 18:46:22 +0000
2067@@ -0,0 +1,66 @@
2068+package elb_test
2069+
2070+import (
2071+ "launchpad.net/goamz/aws"
2072+ "launchpad.net/goamz/elb"
2073+ . "launchpad.net/gocheck"
2074+)
2075+
2076+var testAuth = aws.Auth{"user", "secret"}
2077+
2078+func (s *S) TestBasicSignature(c *C) {
2079+ params := map[string]string{}
2080+ elb.Sign(testAuth, "GET", "/path", params, "localhost")
2081+ c.Assert(params["SignatureVersion"], Equals, "2")
2082+ c.Assert(params["SignatureMethod"], Equals, "HmacSHA256")
2083+ expected := "6lSe5QyXum0jMVc7cOUz32/52ZnL7N5RyKRk/09yiK4="
2084+ c.Assert(params["Signature"], Equals, expected)
2085+}
2086+
2087+func (s *S) TestParamSignature(c *C) {
2088+ params := map[string]string{
2089+ "param1": "value1",
2090+ "param2": "value2",
2091+ "param3": "value3",
2092+ }
2093+ elb.Sign(testAuth, "GET", "/path", params, "localhost")
2094+ expected := "XWOR4+0lmK8bD8CGDGZ4kfuSPbb2JibLJiCl/OPu1oU="
2095+ c.Assert(params["Signature"], Equals, expected)
2096+}
2097+
2098+func (s *S) TestManyParams(c *C) {
2099+ params := map[string]string{
2100+ "param1": "value10",
2101+ "param2": "value2",
2102+ "param3": "value3",
2103+ "param4": "value4",
2104+ "param5": "value5",
2105+ "param6": "value6",
2106+ "param7": "value7",
2107+ "param8": "value8",
2108+ "param9": "value9",
2109+ "param10": "value1",
2110+ }
2111+ elb.Sign(testAuth, "GET", "/path", params, "localhost")
2112+ expected := "di0sjxIvezUgQ1SIL6i+C/H8lL+U0CQ9frLIak8jkVg="
2113+ c.Assert(params["Signature"], Equals, expected)
2114+}
2115+
2116+func (s *S) TestEscaping(c *C) {
2117+ params := map[string]string{"Nonce": "+ +"}
2118+ elb.Sign(testAuth, "GET", "/path", params, "localhost")
2119+ c.Assert(params["Nonce"], Equals, "+ +")
2120+ expected := "bqffDELReIqwjg/W0DnsnVUmfLK4wXVLO4/LuG+1VFA="
2121+ c.Assert(params["Signature"], Equals, expected)
2122+}
2123+
2124+func (s *S) TestSignatureExample1(c *C) {
2125+ params := map[string]string{
2126+ "Timestamp": "2009-02-01T12:53:20+00:00",
2127+ "Version": "2007-11-07",
2128+ "Action": "ListDomains",
2129+ }
2130+ elb.Sign(aws.Auth{"access", "secret"}, "GET", "/", params, "sdb.amazonaws.com")
2131+ expected := "okj96/5ucWBSc1uR2zXVfm6mDHtgfNv657rRtt/aunQ="
2132+ c.Assert(params["Signature"], Equals, expected)
2133+}
2134
2135=== added file 'elb/suite_test.go'
2136--- elb/suite_test.go 1970-01-01 00:00:00 +0000
2137+++ elb/suite_test.go 2013-03-08 18:46:22 +0000
2138@@ -0,0 +1,118 @@
2139+package elb_test
2140+
2141+import (
2142+ "fmt"
2143+ . "launchpad.net/gocheck"
2144+ "net/http"
2145+ "net/url"
2146+ "os"
2147+ "testing"
2148+ "time"
2149+)
2150+
2151+func Test(t *testing.T) {
2152+ TestingT(t)
2153+}
2154+
2155+type HTTPSuite struct{}
2156+
2157+var testServer = NewTestHTTPServer("http://localhost:4444", 5*time.Second)
2158+
2159+func (s *HTTPSuite) SetUpSuite(c *C) {
2160+ testServer.Start()
2161+}
2162+
2163+func (s *HTTPSuite) TearDownTest(c *C) {
2164+ testServer.FlushRequests()
2165+}
2166+
2167+type TestHTTPServer struct {
2168+ URL string
2169+ Timeout time.Duration
2170+ started bool
2171+ request chan *http.Request
2172+ response chan *testResponse
2173+ pending chan bool
2174+}
2175+
2176+type testResponse struct {
2177+ Status int
2178+ Headers map[string]string
2179+ Body string
2180+}
2181+
2182+func NewTestHTTPServer(url string, timeout time.Duration) *TestHTTPServer {
2183+ return &TestHTTPServer{URL: url, Timeout: timeout}
2184+}
2185+
2186+func (s *TestHTTPServer) Start() {
2187+ if s.started {
2188+ return
2189+ }
2190+ s.started = true
2191+
2192+ s.request = make(chan *http.Request, 64)
2193+ s.response = make(chan *testResponse, 64)
2194+ s.pending = make(chan bool, 64)
2195+
2196+ url, _ := url.Parse(s.URL)
2197+ go http.ListenAndServe(url.Host, s)
2198+
2199+ s.PrepareResponse(202, nil, "Nothing.")
2200+ for {
2201+ // Wait for it to be up.
2202+ resp, err := http.Get(s.URL)
2203+ if err == nil && resp.StatusCode == 202 {
2204+ break
2205+ }
2206+ time.Sleep(1e8)
2207+ }
2208+ s.WaitRequest() // Consume dummy request.
2209+}
2210+
2211+// FlushRequests discards requests which were not yet consumed by WaitRequest.
2212+func (s *TestHTTPServer) FlushRequests() {
2213+ for {
2214+ select {
2215+ case <-s.request:
2216+ default:
2217+ return
2218+ }
2219+ }
2220+}
2221+
2222+func (s *TestHTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
2223+ s.request <- req
2224+ var resp *testResponse
2225+ select {
2226+ case resp = <-s.response:
2227+ case <-time.After(s.Timeout):
2228+ fmt.Fprintf(os.Stderr, "ERROR: Timeout waiting for test to provide response\n")
2229+ resp = &testResponse{500, nil, ""}
2230+ }
2231+ if resp.Headers != nil {
2232+ h := w.Header()
2233+ for k, v := range resp.Headers {
2234+ h.Set(k, v)
2235+ }
2236+ }
2237+ if resp.Status != 0 {
2238+ w.WriteHeader(resp.Status)
2239+ }
2240+ w.Write([]byte(resp.Body))
2241+}
2242+
2243+func (s *TestHTTPServer) WaitRequest() *http.Request {
2244+ select {
2245+ case req := <-s.request:
2246+ req.ParseForm()
2247+ return req
2248+ case <-time.After(s.Timeout):
2249+ panic("Timeout waiting for goamz request")
2250+ }
2251+ panic("unreached")
2252+}
2253+
2254+func (s *TestHTTPServer) PrepareResponse(status int, headers map[string]string, body string) {
2255+ s.response <- &testResponse{status, headers, body}
2256+}

Subscribers

People subscribed via source and target branches