summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/goamz/goamz/elb/elbtest/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/goamz/goamz/elb/elbtest/server.go')
-rw-r--r--vendor/github.com/goamz/goamz/elb/elbtest/server.go551
1 files changed, 551 insertions, 0 deletions
diff --git a/vendor/github.com/goamz/goamz/elb/elbtest/server.go b/vendor/github.com/goamz/goamz/elb/elbtest/server.go
new file mode 100644
index 000000000..9b8f79d4e
--- /dev/null
+++ b/vendor/github.com/goamz/goamz/elb/elbtest/server.go
@@ -0,0 +1,551 @@
+// Package elbtest implements a fake ELB provider with the capability of
+// inducing errors on any given operation, and retrospectively determining what
+// operations have been carried out.
+package elbtest
+
+import (
+ "encoding/xml"
+ "fmt"
+ "github.com/goamz/goamz/elb"
+ "net"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+// Server implements an ELB simulator for use in testing.
+type Server struct {
+ url string
+ listener net.Listener
+ mutex sync.Mutex
+ reqId int
+ lbs map[string]*elb.LoadBalancerDescription
+ lbsReqs map[string]url.Values
+ instances []string
+ instanceStates map[string][]*elb.InstanceState
+ instCount int
+}
+
+// Starts and returns a new server
+func NewServer() (*Server, error) {
+ l, err := net.Listen("tcp", "localhost:0")
+ if err != nil {
+ return nil, fmt.Errorf("cannot listen on localhost: %v", err)
+ }
+ srv := &Server{
+ listener: l,
+ url: "http://" + l.Addr().String(),
+ lbs: make(map[string]*elb.LoadBalancerDescription),
+ instanceStates: make(map[string][]*elb.InstanceState),
+ }
+ go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ srv.serveHTTP(w, req)
+ }))
+ return srv, nil
+}
+
+// Quit closes down the server.
+func (srv *Server) Quit() {
+ srv.listener.Close()
+}
+
+// URL returns the URL of the server.
+func (srv *Server) URL() string {
+ return srv.url
+}
+
+type xmlErrors struct {
+ XMLName string `xml:"ErrorResponse"`
+ Error elb.Error
+}
+
+func (srv *Server) error(w http.ResponseWriter, err *elb.Error) {
+ w.WriteHeader(err.StatusCode)
+ xmlErr := xmlErrors{Error: *err}
+ if e := xml.NewEncoder(w).Encode(xmlErr); e != nil {
+ panic(e)
+ }
+}
+
+func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
+ req.ParseForm()
+ srv.mutex.Lock()
+ defer srv.mutex.Unlock()
+ f := actions[req.Form.Get("Action")]
+ if f == nil {
+ srv.error(w, &elb.Error{
+ StatusCode: 400,
+ Code: "InvalidParameterValue",
+ Message: "Unrecognized Action",
+ })
+ }
+ reqId := fmt.Sprintf("req%0X", srv.reqId)
+ srv.reqId++
+ if resp, err := f(srv, w, req, reqId); err == nil {
+ if err := xml.NewEncoder(w).Encode(resp); err != nil {
+ panic(err)
+ }
+ } else {
+ switch err.(type) {
+ case *elb.Error:
+ srv.error(w, err.(*elb.Error))
+ default:
+ panic(err)
+ }
+ }
+}
+
+func (srv *Server) createLoadBalancer(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
+ composition := map[string]string{
+ "AvailabilityZones.member.1": "Subnets.member.1",
+ }
+ if err := srv.validateComposition(req, composition); err != nil {
+ return nil, err
+ }
+ required := []string{
+ "Listeners.member.1.InstancePort",
+ "Listeners.member.1.InstanceProtocol",
+ "Listeners.member.1.Protocol",
+ "Listeners.member.1.LoadBalancerPort",
+ "LoadBalancerName",
+ }
+ if err := srv.validate(req, required); err != nil {
+ return nil, err
+ }
+ path := req.FormValue("Path")
+ if path == "" {
+ path = "/"
+ }
+ lbName := req.FormValue("LoadBalancerName")
+ srv.lbs[lbName] = srv.makeLoadBalancerDescription(req.Form)
+ srv.lbs[lbName].DNSName = fmt.Sprintf("%s-some-aws-stuff.us-east-1.elb.amazonaws.com", lbName)
+ return elb.CreateLoadBalancerResp{
+ DNSName: srv.lbs[lbName].DNSName,
+ }, nil
+}
+
+func (srv *Server) deleteLoadBalancer(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
+ if err := srv.validate(req, []string{"LoadBalancerName"}); err != nil {
+ return nil, err
+ }
+ srv.RemoveLoadBalancer(req.FormValue("LoadBalancerName"))
+ return elb.SimpleResp{RequestId: reqId}, nil
+}
+
+func (srv *Server) registerInstancesWithLoadBalancer(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
+ required := []string{"LoadBalancerName", "Instances.member.1.InstanceId"}
+ if err := srv.validate(req, required); err != nil {
+ return nil, err
+ }
+ lbName := req.FormValue("LoadBalancerName")
+ if err := srv.lbExists(lbName); err != nil {
+ return nil, err
+ }
+ instIds := []string{}
+ instances := []elb.Instance{}
+ i := 1
+ instId := req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
+ for instId != "" {
+ if err := srv.instanceExists(instId); err != nil {
+ return nil, err
+ }
+ instIds = append(instIds, instId)
+ instances = append(instances, elb.Instance{InstanceId: instId})
+ i++
+ instId = req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
+ }
+ srv.instanceStates[lbName] = append(srv.instanceStates[lbName], srv.makeInstanceState(instId))
+ srv.lbs[lbName].Instances = append(srv.lbs[lbName].Instances, instances...)
+ return elb.RegisterInstancesResp{InstanceIds: instIds}, nil
+}
+
+func (srv *Server) deregisterInstancesFromLoadBalancer(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
+ required := []string{"LoadBalancerName"}
+ if err := srv.validate(req, required); err != nil {
+ return nil, err
+ }
+ lbName := req.FormValue("LoadBalancerName")
+ if err := srv.lbExists(lbName); err != nil {
+ return nil, err
+ }
+ i := 1
+ lb := srv.lbs[lbName]
+ instId := req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
+ for instId != "" {
+ if err := srv.instanceExists(instId); err != nil {
+ return nil, err
+ }
+ i++
+ removeInstanceFromLB(lb, instId)
+ instId = req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
+ }
+ srv.lbs[lbName] = lb
+ srv.removeInstanceStatesFromLoadBalancer(lbName, instId)
+ return elb.SimpleResp{RequestId: reqId}, nil
+}
+
+func (srv *Server) describeLoadBalancers(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
+ i := 1
+ lbName := req.FormValue(fmt.Sprintf("LoadBalancerNames.member.%d", i))
+ for lbName != "" {
+ key := fmt.Sprintf("LoadBalancerNames.member.%d", i)
+ if req.FormValue(key) != "" {
+ if err := srv.lbExists(req.FormValue(key)); err != nil {
+ return nil, err
+ }
+ }
+ i++
+ lbName = req.FormValue(fmt.Sprintf("LoadBalancerNames.member.%d", i))
+ }
+ lbsDesc := make([]elb.LoadBalancerDescription, len(srv.lbs))
+ i = 0
+ for _, lb := range srv.lbs {
+ lbsDesc[i] = *lb
+ i++
+ }
+ resp := elb.DescribeLoadBalancerResp{
+ LoadBalancerDescriptions: lbsDesc,
+ }
+ return resp, nil
+}
+
+// getParameters returns the value all parameters from a request that matches a
+// prefix.
+//
+// For example, for the prefix "Subnets.member.", it will return a slice
+// containing the value of keys "Subnets.member.1", "Subnets.member.2" ...
+// "Subnets.member.N". The prefix must include the trailing dot.
+func (srv *Server) getParameters(prefix string, values url.Values) []string {
+ i, key := 1, ""
+ var k = func(n int) string {
+ return fmt.Sprintf(prefix+"%d", n)
+ }
+ var result []string
+ for i, key = 2, k(i); values.Get(key) != ""; i, key = i+1, k(i) {
+ result = append(result, values.Get(key))
+ }
+ return result
+}
+
+func (srv *Server) makeInstanceState(id string) *elb.InstanceState {
+ return &elb.InstanceState{
+ Description: "Instance is in pending state.",
+ InstanceId: id,
+ State: "OutOfService",
+ ReasonCode: "Instance",
+ }
+}
+
+func removeInstanceFromLB(lb *elb.LoadBalancerDescription, id string) {
+ index := -1
+ for i, instance := range lb.Instances {
+ if instance.InstanceId == id {
+ index = i
+ break
+ }
+ }
+ if index > -1 {
+ copy(lb.Instances[index:], lb.Instances[index+1:])
+ lb.Instances = lb.Instances[:len(lb.Instances)-1]
+ }
+}
+
+func (srv *Server) removeInstanceStatesFromLoadBalancer(lb, id string) {
+ for i, state := range srv.instanceStates[lb] {
+ if state.InstanceId == id {
+ a := srv.instanceStates[lb]
+ a[i], a = a[len(a)-1], a[:len(a)-1]
+ srv.instanceStates[lb] = a
+ return
+ }
+ }
+}
+
+func (srv *Server) makeLoadBalancerDescription(value url.Values) *elb.LoadBalancerDescription {
+ lds := []elb.ListenerDescription{}
+ i := 1
+ protocol := value.Get(fmt.Sprintf("Listeners.member.%d.Protocol", i))
+ for protocol != "" {
+ key := fmt.Sprintf("Listeners.member.%d.", i)
+ lInstPort, _ := strconv.Atoi(value.Get(key + "InstancePort"))
+ lLBPort, _ := strconv.Atoi(value.Get(key + "LoadBalancerPort"))
+ lDescription := elb.ListenerDescription{
+ Listener: elb.Listener{
+ Protocol: strings.ToUpper(protocol),
+ InstanceProtocol: strings.ToUpper(value.Get(key + "InstanceProtocol")),
+ LoadBalancerPort: lLBPort,
+ InstancePort: lInstPort,
+ },
+ }
+ i++
+ protocol = value.Get(fmt.Sprintf("Listeners.member.%d.Protocol", i))
+ lds = append(lds, lDescription)
+ }
+ sourceSecGroup := srv.makeSourceSecGroup(value)
+ lbDesc := elb.LoadBalancerDescription{
+ AvailabilityZones: srv.getParameters("AvailabilityZones.member.", value),
+ Subnets: srv.getParameters("Subnets.member.", value),
+ SecurityGroups: srv.getParameters("SecurityGroups.member.", value),
+ HealthCheck: srv.makeHealthCheck(value),
+ ListenerDescriptions: lds,
+ Scheme: value.Get("Scheme"),
+ SourceSecurityGroup: sourceSecGroup,
+ LoadBalancerName: value.Get("LoadBalancerName"),
+ }
+ if lbDesc.Scheme == "" {
+ lbDesc.Scheme = "internet-facing"
+ }
+ return &lbDesc
+}
+
+func (srv *Server) makeHealthCheck(value url.Values) elb.HealthCheck {
+ ht := 10
+ timeout := 5
+ ut := 2
+ interval := 30
+ target := "TCP:80"
+ if v := value.Get("HealthCheck.HealthyThreshold"); v != "" {
+ ht, _ = strconv.Atoi(v)
+ }
+ if v := value.Get("HealthCheck.Timeout"); v != "" {
+ timeout, _ = strconv.Atoi(v)
+ }
+ if v := value.Get("HealthCheck.UnhealthyThreshold"); v != "" {
+ ut, _ = strconv.Atoi(v)
+ }
+ if v := value.Get("HealthCheck.Interval"); v != "" {
+ interval, _ = strconv.Atoi(v)
+ }
+ if v := value.Get("HealthCheck.Target"); v != "" {
+ target = v
+ }
+ return elb.HealthCheck{
+ HealthyThreshold: ht,
+ Interval: interval,
+ Target: target,
+ Timeout: timeout,
+ UnhealthyThreshold: ut,
+ }
+}
+
+func (srv *Server) makeSourceSecGroup(value url.Values) elb.SourceSecurityGroup {
+ name := "amazon-elb-sg"
+ alias := "amazon-elb"
+ if v := value.Get("SourceSecurityGroup.GroupName"); v != "" {
+ name = v
+ }
+ if v := value.Get("SourceSecurityGroup.OwnerAlias"); v != "" {
+ alias = v
+ }
+ return elb.SourceSecurityGroup{
+ GroupName: name,
+ OwnerAlias: alias,
+ }
+}
+
+func (srv *Server) describeInstanceHealth(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
+ if err := srv.lbExists(req.FormValue("LoadBalancerName")); err != nil {
+ return nil, err
+ }
+ resp := elb.DescribeInstanceHealthResp{
+ InstanceStates: []elb.InstanceState{},
+ }
+ for _, state := range srv.instanceStates[req.FormValue("LoadBalancerName")] {
+ resp.InstanceStates = append(resp.InstanceStates, *state)
+ }
+ i := 1
+ instanceId := req.FormValue("Instances.member.1.InstanceId")
+ for instanceId != "" {
+ if err := srv.instanceExists(instanceId); err != nil {
+ return nil, err
+ }
+ is := elb.InstanceState{
+ Description: "Instance is in pending state.",
+ InstanceId: instanceId,
+ State: "OutOfService",
+ ReasonCode: "Instance",
+ }
+ resp.InstanceStates = append(resp.InstanceStates, is)
+ i++
+ instanceId = req.FormValue(fmt.Sprintf("Instances.member.%d.InstanceId", i))
+ }
+ return resp, nil
+}
+
+func (srv *Server) configureHealthCheck(w http.ResponseWriter, req *http.Request, reqId string) (interface{}, error) {
+ required := []string{
+ "LoadBalancerName",
+ "HealthCheck.HealthyThreshold",
+ "HealthCheck.Interval",
+ "HealthCheck.Target",
+ "HealthCheck.Timeout",
+ "HealthCheck.UnhealthyThreshold",
+ }
+ if err := srv.validate(req, required); err != nil {
+ return nil, err
+ }
+ target := req.FormValue("HealthCheck.Target")
+ r, err := regexp.Compile(`[\w]+:[\d]+\/+`)
+ if err != nil {
+ panic(err)
+ }
+ if m := r.FindStringSubmatch(target); m == nil {
+ return nil, &elb.Error{
+ StatusCode: 400,
+ Code: "ValidationError",
+ Message: "HealthCheck HTTP Target must specify a port followed by a path that begins with a slash. e.g. HTTP:80/ping/this/path",
+ }
+ }
+ ht, _ := strconv.Atoi(req.FormValue("HealthCheck.HealthyThreshold"))
+ interval, _ := strconv.Atoi(req.FormValue("HealthCheck.Interval"))
+ timeout, _ := strconv.Atoi(req.FormValue("HealthCheck.Timeout"))
+ ut, _ := strconv.Atoi(req.FormValue("HealthCheck.UnhealthyThreshold"))
+ return elb.HealthCheckResp{
+ HealthCheck: &elb.HealthCheck{
+ HealthyThreshold: ht,
+ Interval: interval,
+ Target: target,
+ Timeout: timeout,
+ UnhealthyThreshold: ut,
+ },
+ }, nil
+}
+
+func (srv *Server) instanceExists(id string) error {
+ for _, instId := range srv.instances {
+ if instId == id {
+ return nil
+ }
+ }
+ return &elb.Error{
+ StatusCode: 400,
+ Code: "InvalidInstance",
+ Message: fmt.Sprintf("InvalidInstance found in [%s]. Invalid id: \"%s\"", id, id),
+ }
+}
+
+func (srv *Server) lbExists(name string) error {
+ if _, ok := srv.lbs[name]; !ok {
+ return &elb.Error{
+ StatusCode: 400,
+ Code: "LoadBalancerNotFound",
+ Message: fmt.Sprintf("There is no ACTIVE Load Balancer named '%s'", name),
+ }
+ }
+ return nil
+}
+
+func (srv *Server) validate(req *http.Request, required []string) error {
+ for _, field := range required {
+ if req.FormValue(field) == "" {
+ return &elb.Error{
+ StatusCode: 400,
+ Code: "ValidationError",
+ Message: fmt.Sprintf("%s is required.", field),
+ }
+ }
+ }
+ return nil
+}
+
+// Validates the composition of the fields.
+//
+// Some fields cannot be together in the same request, such as AvailabilityZones and Subnets.
+// A sample map with the above requirement would be
+// c := map[string]string{
+// "AvailabilityZones.member.1": "Subnets.member.1",
+// }
+//
+// The server also requires that at least one of those fields are specified.
+func (srv *Server) validateComposition(req *http.Request, composition map[string]string) error {
+ for k, v := range composition {
+ if req.FormValue(k) != "" && req.FormValue(v) != "" {
+ return &elb.Error{
+ StatusCode: 400,
+ Code: "ValidationError",
+ Message: fmt.Sprintf("Only one of %s or %s may be specified", k, v),
+ }
+ }
+ if req.FormValue(k) == "" && req.FormValue(v) == "" {
+ return &elb.Error{
+ StatusCode: 400,
+ Code: "ValidationError",
+ Message: fmt.Sprintf("Either %s or %s must be specified", k, v),
+ }
+ }
+ }
+ return nil
+}
+
+// Creates a fake instance in the server
+func (srv *Server) NewInstance() string {
+ srv.instCount++
+ instId := fmt.Sprintf("i-%d", srv.instCount)
+ srv.instances = append(srv.instances, instId)
+ return instId
+}
+
+// Removes a fake instance from the server
+//
+// If no instance is found it does nothing
+func (srv *Server) RemoveInstance(instId string) {
+ for i, id := range srv.instances {
+ if id == instId {
+ srv.instances[i], srv.instances = srv.instances[len(srv.instances)-1], srv.instances[:len(srv.instances)-1]
+ }
+ }
+}
+
+// Creates a fake load balancer in the fake server
+func (srv *Server) NewLoadBalancer(name string) {
+ srv.lbs[name] = &elb.LoadBalancerDescription{
+ LoadBalancerName: name,
+ DNSName: fmt.Sprintf("%s-some-aws-stuff.sa-east-1.amazonaws.com", name),
+ }
+}
+
+// Removes a fake load balancer from the fake server
+func (srv *Server) RemoveLoadBalancer(name string) {
+ delete(srv.lbs, name)
+}
+
+// Register a fake instance with a fake Load Balancer
+//
+// If the Load Balancer does not exists it does nothing
+func (srv *Server) RegisterInstance(instId, lbName string) {
+ lb, ok := srv.lbs[lbName]
+ if !ok {
+ fmt.Println("lb not found :/")
+ return
+ }
+ lb.Instances = append(lb.Instances, elb.Instance{InstanceId: instId})
+ srv.instanceStates[lbName] = append(srv.instanceStates[lbName], srv.makeInstanceState(instId))
+}
+
+func (srv *Server) DeregisterInstance(instId, lbName string) {
+ removeInstanceFromLB(srv.lbs[lbName], instId)
+ srv.removeInstanceStatesFromLoadBalancer(lbName, instId)
+}
+
+func (srv *Server) ChangeInstanceState(lb string, state elb.InstanceState) {
+ states := srv.instanceStates[lb]
+ for i, s := range states {
+ if s.InstanceId == state.InstanceId {
+ srv.instanceStates[lb][i] = &state
+ return
+ }
+ }
+}
+
+var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) (interface{}, error){
+ "CreateLoadBalancer": (*Server).createLoadBalancer,
+ "DeleteLoadBalancer": (*Server).deleteLoadBalancer,
+ "RegisterInstancesWithLoadBalancer": (*Server).registerInstancesWithLoadBalancer,
+ "DeregisterInstancesFromLoadBalancer": (*Server).deregisterInstancesFromLoadBalancer,
+ "DescribeLoadBalancers": (*Server).describeLoadBalancers,
+ "DescribeInstanceHealth": (*Server).describeInstanceHealth,
+ "ConfigureHealthCheck": (*Server).configureHealthCheck,
+}