summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/goamz/goamz/ec2/ec2test/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/goamz/goamz/ec2/ec2test/server.go')
-rw-r--r--vendor/github.com/goamz/goamz/ec2/ec2test/server.go993
1 files changed, 993 insertions, 0 deletions
diff --git a/vendor/github.com/goamz/goamz/ec2/ec2test/server.go b/vendor/github.com/goamz/goamz/ec2/ec2test/server.go
new file mode 100644
index 000000000..e25d4ea20
--- /dev/null
+++ b/vendor/github.com/goamz/goamz/ec2/ec2test/server.go
@@ -0,0 +1,993 @@
+// The ec2test package implements a fake EC2 provider with
+// the capability of inducing errors on any given operation,
+// and retrospectively determining what operations have been
+// carried out.
+package ec2test
+
+import (
+ "encoding/base64"
+ "encoding/xml"
+ "fmt"
+ "github.com/goamz/goamz/ec2"
+ "io"
+ "net"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+var b64 = base64.StdEncoding
+
+// Action represents a request that changes the ec2 state.
+type Action struct {
+ RequestId string
+
+ // Request holds the requested action as a url.Values instance
+ Request url.Values
+
+ // If the action succeeded, Response holds the value that
+ // was marshalled to build the XML response for the request.
+ Response interface{}
+
+ // If the action failed, Err holds an error giving details of the failure.
+ Err *ec2.Error
+}
+
+// TODO possible other things:
+// - some virtual time stamp interface, so a client
+// can ask for all actions after a certain virtual time.
+
+// Server implements an EC2 simulator for use in testing.
+type Server struct {
+ url string
+ listener net.Listener
+ mu sync.Mutex
+ reqs []*Action
+
+ instances map[string]*Instance // id -> instance
+ reservations map[string]*reservation // id -> reservation
+ groups map[string]*securityGroup // id -> group
+ maxId counter
+ reqId counter
+ reservationId counter
+ groupId counter
+ initialInstanceState ec2.InstanceState
+}
+
+// reservation holds a simulated ec2 reservation.
+type reservation struct {
+ id string
+ instances map[string]*Instance
+ groups []*securityGroup
+}
+
+// instance holds a simulated ec2 instance
+type Instance struct {
+ // UserData holds the data that was passed to the RunInstances request
+ // when the instance was started.
+ UserData []byte
+ id string
+ imageId string
+ reservation *reservation
+ instType string
+ state ec2.InstanceState
+}
+
+// permKey represents permission for a given security
+// group or IP address (but not both) to access a given range of
+// ports. Equality of permKeys is used in the implementation of
+// permission sets, relying on the uniqueness of securityGroup
+// instances.
+type permKey struct {
+ protocol string
+ fromPort int
+ toPort int
+ group *securityGroup
+ ipAddr string
+}
+
+// securityGroup holds a simulated ec2 security group.
+// Instances of securityGroup should only be created through
+// Server.createSecurityGroup to ensure that groups can be
+// compared by pointer value.
+type securityGroup struct {
+ id string
+ name string
+ description string
+
+ perms map[permKey]bool
+}
+
+func (g *securityGroup) ec2SecurityGroup() ec2.SecurityGroup {
+ return ec2.SecurityGroup{
+ Name: g.name,
+ Id: g.id,
+ }
+}
+
+func (g *securityGroup) matchAttr(attr, value string) (ok bool, err error) {
+ switch attr {
+ case "description":
+ return g.description == value, nil
+ case "group-id":
+ return g.id == value, nil
+ case "group-name":
+ return g.name == value, nil
+ case "ip-permission.cidr":
+ return g.hasPerm(func(k permKey) bool { return k.ipAddr == value }), nil
+ case "ip-permission.group-name":
+ return g.hasPerm(func(k permKey) bool {
+ return k.group != nil && k.group.name == value
+ }), nil
+ case "ip-permission.from-port":
+ port, err := strconv.Atoi(value)
+ if err != nil {
+ return false, err
+ }
+ return g.hasPerm(func(k permKey) bool { return k.fromPort == port }), nil
+ case "ip-permission.to-port":
+ port, err := strconv.Atoi(value)
+ if err != nil {
+ return false, err
+ }
+ return g.hasPerm(func(k permKey) bool { return k.toPort == port }), nil
+ case "ip-permission.protocol":
+ return g.hasPerm(func(k permKey) bool { return k.protocol == value }), nil
+ case "owner-id":
+ return value == ownerId, nil
+ }
+ return false, fmt.Errorf("unknown attribute %q", attr)
+}
+
+func (g *securityGroup) hasPerm(test func(k permKey) bool) bool {
+ for k := range g.perms {
+ if test(k) {
+ return true
+ }
+ }
+ return false
+}
+
+// ec2Perms returns the list of EC2 permissions granted
+// to g. It groups permissions by port range and protocol.
+func (g *securityGroup) ec2Perms() (perms []ec2.IPPerm) {
+ // The grouping is held in result. We use permKey for convenience,
+ // (ensuring that the group and ipAddr of each key is zero). For
+ // each protocol/port range combination, we build up the permission
+ // set in the associated value.
+ result := make(map[permKey]*ec2.IPPerm)
+ for k := range g.perms {
+ groupKey := k
+ groupKey.group = nil
+ groupKey.ipAddr = ""
+
+ ec2p := result[groupKey]
+ if ec2p == nil {
+ ec2p = &ec2.IPPerm{
+ Protocol: k.protocol,
+ FromPort: k.fromPort,
+ ToPort: k.toPort,
+ }
+ result[groupKey] = ec2p
+ }
+ if k.group != nil {
+ ec2p.SourceGroups = append(ec2p.SourceGroups,
+ ec2.UserSecurityGroup{
+ Id: k.group.id,
+ Name: k.group.name,
+ OwnerId: ownerId,
+ })
+ } else {
+ ec2p.SourceIPs = append(ec2p.SourceIPs, k.ipAddr)
+ }
+ }
+ for _, ec2p := range result {
+ perms = append(perms, *ec2p)
+ }
+ return
+}
+
+var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) interface{}{
+ "RunInstances": (*Server).runInstances,
+ "TerminateInstances": (*Server).terminateInstances,
+ "DescribeInstances": (*Server).describeInstances,
+ "CreateSecurityGroup": (*Server).createSecurityGroup,
+ "DescribeSecurityGroups": (*Server).describeSecurityGroups,
+ "DeleteSecurityGroup": (*Server).deleteSecurityGroup,
+ "AuthorizeSecurityGroupIngress": (*Server).authorizeSecurityGroupIngress,
+ "RevokeSecurityGroupIngress": (*Server).revokeSecurityGroupIngress,
+}
+
+const ownerId = "9876"
+
+// newAction allocates a new action and adds it to the
+// recorded list of server actions.
+func (srv *Server) newAction() *Action {
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+
+ a := new(Action)
+ srv.reqs = append(srv.reqs, a)
+ return a
+}
+
+// NewServer returns a new server.
+func NewServer() (*Server, error) {
+ srv := &Server{
+ instances: make(map[string]*Instance),
+ groups: make(map[string]*securityGroup),
+ reservations: make(map[string]*reservation),
+ initialInstanceState: Pending,
+ }
+
+ // Add default security group.
+ g := &securityGroup{
+ name: "default",
+ description: "default group",
+ id: fmt.Sprintf("sg-%d", srv.groupId.next()),
+ }
+ g.perms = map[permKey]bool{
+ permKey{
+ protocol: "icmp",
+ fromPort: -1,
+ toPort: -1,
+ group: g,
+ }: true,
+ permKey{
+ protocol: "tcp",
+ fromPort: 0,
+ toPort: 65535,
+ group: g,
+ }: true,
+ permKey{
+ protocol: "udp",
+ fromPort: 0,
+ toPort: 65535,
+ group: g,
+ }: true,
+ }
+ srv.groups[g.id] = g
+
+ l, err := net.Listen("tcp", "localhost:0")
+ if err != nil {
+ return nil, fmt.Errorf("cannot listen on localhost: %v", err)
+ }
+ srv.listener = l
+
+ srv.url = "http://" + l.Addr().String()
+
+ // we use HandlerFunc rather than *Server directly so that we
+ // can avoid exporting HandlerFunc from *Server.
+ 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()
+}
+
+// SetInitialInstanceState sets the state that any new instances will be started in.
+func (srv *Server) SetInitialInstanceState(state ec2.InstanceState) {
+ srv.mu.Lock()
+ srv.initialInstanceState = state
+ srv.mu.Unlock()
+}
+
+// URL returns the URL of the server.
+func (srv *Server) URL() string {
+ return srv.url
+}
+
+// serveHTTP serves the EC2 protocol.
+func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
+ req.ParseForm()
+
+ a := srv.newAction()
+ a.RequestId = fmt.Sprintf("req%d", srv.reqId.next())
+ a.Request = req.Form
+
+ // Methods on Server that deal with parsing user data
+ // may fail. To save on error handling code, we allow these
+ // methods to call fatalf, which will panic with an *ec2.Error
+ // which will be caught here and returned
+ // to the client as a properly formed EC2 error.
+ defer func() {
+ switch err := recover().(type) {
+ case *ec2.Error:
+ a.Err = err
+ err.RequestId = a.RequestId
+ writeError(w, err)
+ case nil:
+ default:
+ panic(err)
+ }
+ }()
+
+ f := actions[req.Form.Get("Action")]
+ if f == nil {
+ fatalf(400, "InvalidParameterValue", "Unrecognized Action")
+ }
+
+ response := f(srv, w, req, a.RequestId)
+ a.Response = response
+
+ w.Header().Set("Content-Type", `xml version="1.0" encoding="UTF-8"`)
+ xmlMarshal(w, response)
+}
+
+// Instance returns the instance for the given instance id.
+// It returns nil if there is no such instance.
+func (srv *Server) Instance(id string) *Instance {
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+ return srv.instances[id]
+}
+
+// writeError writes an appropriate error response.
+// TODO how should we deal with errors when the
+// error itself is potentially generated by backend-agnostic
+// code?
+func writeError(w http.ResponseWriter, err *ec2.Error) {
+ // Error encapsulates an error returned by EC2.
+ // TODO merge with ec2.Error when xml supports ignoring a field.
+ type ec2error struct {
+ Code string // EC2 error code ("UnsupportedOperation", ...)
+ Message string // The human-oriented error message
+ RequestId string
+ }
+
+ type Response struct {
+ RequestId string
+ Errors []ec2error `xml:"Errors>Error"`
+ }
+
+ w.Header().Set("Content-Type", `xml version="1.0" encoding="UTF-8"`)
+ w.WriteHeader(err.StatusCode)
+ xmlMarshal(w, Response{
+ RequestId: err.RequestId,
+ Errors: []ec2error{{
+ Code: err.Code,
+ Message: err.Message,
+ }},
+ })
+}
+
+// xmlMarshal is the same as xml.Marshal except that
+// it panics on error. The marshalling should not fail,
+// but we want to know if it does.
+func xmlMarshal(w io.Writer, x interface{}) {
+ if err := xml.NewEncoder(w).Encode(x); err != nil {
+ panic(fmt.Errorf("error marshalling %#v: %v", x, err))
+ }
+}
+
+// formToGroups parses a set of SecurityGroup form values
+// as found in a RunInstances request, and returns the resulting
+// slice of security groups.
+// It calls fatalf if a group is not found.
+func (srv *Server) formToGroups(form url.Values) []*securityGroup {
+ var groups []*securityGroup
+ for name, values := range form {
+ switch {
+ case strings.HasPrefix(name, "SecurityGroupId."):
+ if g := srv.groups[values[0]]; g != nil {
+ groups = append(groups, g)
+ } else {
+ fatalf(400, "InvalidGroup.NotFound", "unknown group id %q", values[0])
+ }
+ case strings.HasPrefix(name, "SecurityGroup."):
+ var found *securityGroup
+ for _, g := range srv.groups {
+ if g.name == values[0] {
+ found = g
+ }
+ }
+ if found == nil {
+ fatalf(400, "InvalidGroup.NotFound", "unknown group name %q", values[0])
+ }
+ groups = append(groups, found)
+ }
+ }
+ return groups
+}
+
+// runInstances implements the EC2 RunInstances entry point.
+func (srv *Server) runInstances(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ min := atoi(req.Form.Get("MinCount"))
+ max := atoi(req.Form.Get("MaxCount"))
+ if min < 0 || max < 1 {
+ fatalf(400, "InvalidParameterValue", "bad values for MinCount or MaxCount")
+ }
+ if min > max {
+ fatalf(400, "InvalidParameterCombination", "MinCount is greater than MaxCount")
+ }
+ var userData []byte
+ if data := req.Form.Get("UserData"); data != "" {
+ var err error
+ userData, err = b64.DecodeString(data)
+ if err != nil {
+ fatalf(400, "InvalidParameterValue", "bad UserData value: %v", err)
+ }
+ }
+
+ // TODO attributes still to consider:
+ // ImageId: accept anything, we can verify later
+ // KeyName ?
+ // InstanceType ?
+ // KernelId ?
+ // RamdiskId ?
+ // AvailabilityZone ?
+ // GroupName tag
+ // Monitoring ignore?
+ // SubnetId ?
+ // DisableAPITermination bool
+ // ShutdownBehavior string
+ // PrivateIPAddress string
+
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+
+ // make sure that form fields are correct before creating the reservation.
+ instType := req.Form.Get("InstanceType")
+ imageId := req.Form.Get("ImageId")
+
+ r := srv.newReservation(srv.formToGroups(req.Form))
+
+ var resp ec2.RunInstancesResp
+ resp.RequestId = reqId
+ resp.ReservationId = r.id
+ resp.OwnerId = ownerId
+
+ for i := 0; i < max; i++ {
+ inst := srv.newInstance(r, instType, imageId, srv.initialInstanceState)
+ inst.UserData = userData
+ resp.Instances = append(resp.Instances, inst.ec2instance())
+ }
+ return &resp
+}
+
+func (srv *Server) group(group ec2.SecurityGroup) *securityGroup {
+ if group.Id != "" {
+ return srv.groups[group.Id]
+ }
+ for _, g := range srv.groups {
+ if g.name == group.Name {
+ return g
+ }
+ }
+ return nil
+}
+
+// NewInstances creates n new instances in srv with the given instance type,
+// image ID, initial state and security groups. If any group does not already
+// exist, it will be created. NewInstances returns the ids of the new instances.
+func (srv *Server) NewInstances(n int, instType string, imageId string, state ec2.InstanceState, groups []ec2.SecurityGroup) []string {
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+
+ rgroups := make([]*securityGroup, len(groups))
+ for i, group := range groups {
+ g := srv.group(group)
+ if g == nil {
+ fatalf(400, "InvalidGroup.NotFound", "no such group %v", g)
+ }
+ rgroups[i] = g
+ }
+ r := srv.newReservation(rgroups)
+
+ ids := make([]string, n)
+ for i := 0; i < n; i++ {
+ inst := srv.newInstance(r, instType, imageId, state)
+ ids[i] = inst.id
+ }
+ return ids
+}
+
+func (srv *Server) newInstance(r *reservation, instType string, imageId string, state ec2.InstanceState) *Instance {
+ inst := &Instance{
+ id: fmt.Sprintf("i-%d", srv.maxId.next()),
+ instType: instType,
+ imageId: imageId,
+ state: state,
+ reservation: r,
+ }
+ srv.instances[inst.id] = inst
+ r.instances[inst.id] = inst
+ return inst
+}
+
+func (srv *Server) newReservation(groups []*securityGroup) *reservation {
+ r := &reservation{
+ id: fmt.Sprintf("r-%d", srv.reservationId.next()),
+ instances: make(map[string]*Instance),
+ groups: groups,
+ }
+
+ srv.reservations[r.id] = r
+ return r
+}
+
+func (srv *Server) terminateInstances(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+ var resp ec2.TerminateInstancesResp
+ resp.RequestId = reqId
+ var insts []*Instance
+ for attr, vals := range req.Form {
+ if strings.HasPrefix(attr, "InstanceId.") {
+ id := vals[0]
+ inst := srv.instances[id]
+ if inst == nil {
+ fatalf(400, "InvalidInstanceID.NotFound", "no such instance id %q", id)
+ }
+ insts = append(insts, inst)
+ }
+ }
+ for _, inst := range insts {
+ resp.StateChanges = append(resp.StateChanges, inst.terminate())
+ }
+ return &resp
+}
+
+func (inst *Instance) terminate() (d ec2.InstanceStateChange) {
+ d.PreviousState = inst.state
+ inst.state = ShuttingDown
+ d.CurrentState = inst.state
+ d.InstanceId = inst.id
+ return d
+}
+
+func (inst *Instance) ec2instance() ec2.Instance {
+ return ec2.Instance{
+ InstanceId: inst.id,
+ InstanceType: inst.instType,
+ ImageId: inst.imageId,
+ DNSName: fmt.Sprintf("%s.example.com", inst.id),
+ // TODO the rest
+ }
+}
+
+func (inst *Instance) matchAttr(attr, value string) (ok bool, err error) {
+ switch attr {
+ case "architecture":
+ return value == "i386", nil
+ case "instance-id":
+ return inst.id == value, nil
+ case "group-id":
+ for _, g := range inst.reservation.groups {
+ if g.id == value {
+ return true, nil
+ }
+ }
+ return false, nil
+ case "group-name":
+ for _, g := range inst.reservation.groups {
+ if g.name == value {
+ return true, nil
+ }
+ }
+ return false, nil
+ case "image-id":
+ return value == inst.imageId, nil
+ case "instance-state-code":
+ code, err := strconv.Atoi(value)
+ if err != nil {
+ return false, err
+ }
+ return code&0xff == inst.state.Code, nil
+ case "instance-state-name":
+ return value == inst.state.Name, nil
+ }
+ return false, fmt.Errorf("unknown attribute %q", attr)
+}
+
+var (
+ Pending = ec2.InstanceState{0, "pending"}
+ Running = ec2.InstanceState{16, "running"}
+ ShuttingDown = ec2.InstanceState{32, "shutting-down"}
+ Terminated = ec2.InstanceState{16, "terminated"}
+ Stopped = ec2.InstanceState{16, "stopped"}
+)
+
+func (srv *Server) createSecurityGroup(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ name := req.Form.Get("GroupName")
+ if name == "" {
+ fatalf(400, "InvalidParameterValue", "empty security group name")
+ }
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+ if srv.group(ec2.SecurityGroup{Name: name}) != nil {
+ fatalf(400, "InvalidGroup.Duplicate", "group %q already exists", name)
+ }
+ g := &securityGroup{
+ name: name,
+ description: req.Form.Get("GroupDescription"),
+ id: fmt.Sprintf("sg-%d", srv.groupId.next()),
+ perms: make(map[permKey]bool),
+ }
+ srv.groups[g.id] = g
+ // we define a local type for this because ec2.CreateSecurityGroupResp
+ // contains SecurityGroup, but the response to this request
+ // should not contain the security group name.
+ type CreateSecurityGroupResponse struct {
+ RequestId string `xml:"requestId"`
+ Return bool `xml:"return"`
+ GroupId string `xml:"groupId"`
+ }
+ r := &CreateSecurityGroupResponse{
+ RequestId: reqId,
+ Return: true,
+ GroupId: g.id,
+ }
+ return r
+}
+
+func (srv *Server) notImplemented(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ fatalf(500, "InternalError", "not implemented")
+ panic("not reached")
+}
+
+func (srv *Server) describeInstances(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+ insts := make(map[*Instance]bool)
+ for name, vals := range req.Form {
+ if !strings.HasPrefix(name, "InstanceId.") {
+ continue
+ }
+ inst := srv.instances[vals[0]]
+ if inst == nil {
+ fatalf(400, "InvalidInstanceID.NotFound", "instance %q not found", vals[0])
+ }
+ insts[inst] = true
+ }
+
+ f := newFilter(req.Form)
+
+ var resp ec2.DescribeInstancesResp
+ resp.RequestId = reqId
+ for _, r := range srv.reservations {
+ var instances []ec2.Instance
+ for _, inst := range r.instances {
+ if len(insts) > 0 && !insts[inst] {
+ continue
+ }
+ ok, err := f.ok(inst)
+ if ok {
+ instances = append(instances, inst.ec2instance())
+ } else if err != nil {
+ fatalf(400, "InvalidParameterValue", "describe instances: %v", err)
+ }
+ }
+ if len(instances) > 0 {
+ var groups []ec2.SecurityGroup
+ for _, g := range r.groups {
+ groups = append(groups, g.ec2SecurityGroup())
+ }
+ resp.Reservations = append(resp.Reservations, ec2.Reservation{
+ ReservationId: r.id,
+ OwnerId: ownerId,
+ Instances: instances,
+ SecurityGroups: groups,
+ })
+ }
+ }
+ return &resp
+}
+
+func (srv *Server) describeSecurityGroups(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ // BUG similar bug to describeInstances, but for GroupName and GroupId
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+
+ var groups []*securityGroup
+ for name, vals := range req.Form {
+ var g ec2.SecurityGroup
+ switch {
+ case strings.HasPrefix(name, "GroupName."):
+ g.Name = vals[0]
+ case strings.HasPrefix(name, "GroupId."):
+ g.Id = vals[0]
+ default:
+ continue
+ }
+ sg := srv.group(g)
+ if sg == nil {
+ fatalf(400, "InvalidGroup.NotFound", "no such group %v", g)
+ }
+ groups = append(groups, sg)
+ }
+ if len(groups) == 0 {
+ for _, g := range srv.groups {
+ groups = append(groups, g)
+ }
+ }
+
+ f := newFilter(req.Form)
+ var resp ec2.SecurityGroupsResp
+ resp.RequestId = reqId
+ for _, group := range groups {
+ ok, err := f.ok(group)
+ if ok {
+ resp.Groups = append(resp.Groups, ec2.SecurityGroupInfo{
+ OwnerId: ownerId,
+ SecurityGroup: group.ec2SecurityGroup(),
+ Description: group.description,
+ IPPerms: group.ec2Perms(),
+ })
+ } else if err != nil {
+ fatalf(400, "InvalidParameterValue", "describe security groups: %v", err)
+ }
+ }
+ return &resp
+}
+
+func (srv *Server) authorizeSecurityGroupIngress(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+ g := srv.group(ec2.SecurityGroup{
+ Name: req.Form.Get("GroupName"),
+ Id: req.Form.Get("GroupId"),
+ })
+ if g == nil {
+ fatalf(400, "InvalidGroup.NotFound", "group not found")
+ }
+ perms := srv.parsePerms(req)
+
+ for _, p := range perms {
+ if g.perms[p] {
+ fatalf(400, "InvalidPermission.Duplicate", "Permission has already been authorized on the specified group")
+ }
+ }
+ for _, p := range perms {
+ g.perms[p] = true
+ }
+ return &ec2.SimpleResp{
+ XMLName: xml.Name{"", "AuthorizeSecurityGroupIngressResponse"},
+ RequestId: reqId,
+ }
+}
+
+func (srv *Server) revokeSecurityGroupIngress(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+ g := srv.group(ec2.SecurityGroup{
+ Name: req.Form.Get("GroupName"),
+ Id: req.Form.Get("GroupId"),
+ })
+ if g == nil {
+ fatalf(400, "InvalidGroup.NotFound", "group not found")
+ }
+ perms := srv.parsePerms(req)
+
+ // Note EC2 does not give an error if asked to revoke an authorization
+ // that does not exist.
+ for _, p := range perms {
+ delete(g.perms, p)
+ }
+ return &ec2.SimpleResp{
+ XMLName: xml.Name{"", "RevokeSecurityGroupIngressResponse"},
+ RequestId: reqId,
+ }
+}
+
+var secGroupPat = regexp.MustCompile(`^sg-[a-z0-9]+$`)
+var ipPat = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$`)
+var ownerIdPat = regexp.MustCompile(`^[0-9]+$`)
+
+// parsePerms returns a slice of permKey values extracted
+// from the permission fields in req.
+func (srv *Server) parsePerms(req *http.Request) []permKey {
+ // perms maps an index found in the form to its associated
+ // IPPerm. For instance, the form value with key
+ // "IpPermissions.3.FromPort" will be stored in perms[3].FromPort
+ perms := make(map[int]ec2.IPPerm)
+
+ type subgroupKey struct {
+ id1, id2 int
+ }
+ // Each IPPerm can have many source security groups. The form key
+ // for a source security group contains two indices: the index
+ // of the IPPerm and the sub-index of the security group. The
+ // sourceGroups map maps from a subgroupKey containing these
+ // two indices to the associated security group. For instance,
+ // the form value with key "IPPermissions.3.Groups.2.GroupName"
+ // will be stored in sourceGroups[subgroupKey{3, 2}].Name.
+ sourceGroups := make(map[subgroupKey]ec2.UserSecurityGroup)
+
+ // For each value in the form we store its associated information in the
+ // above maps. The maps are necessary because the form keys may
+ // arrive in any order, and the indices are not
+ // necessarily sequential or even small.
+ for name, vals := range req.Form {
+ val := vals[0]
+ var id1 int
+ var rest string
+ if x, _ := fmt.Sscanf(name, "IpPermissions.%d.%s", &id1, &rest); x != 2 {
+ continue
+ }
+ ec2p := perms[id1]
+ switch {
+ case rest == "FromPort":
+ ec2p.FromPort = atoi(val)
+ case rest == "ToPort":
+ ec2p.ToPort = atoi(val)
+ case rest == "IpProtocol":
+ switch val {
+ case "tcp", "udp", "icmp":
+ ec2p.Protocol = val
+ default:
+ // check it's a well formed number
+ atoi(val)
+ ec2p.Protocol = val
+ }
+ case strings.HasPrefix(rest, "Groups."):
+ k := subgroupKey{id1: id1}
+ if x, _ := fmt.Sscanf(rest[len("Groups."):], "%d.%s", &k.id2, &rest); x != 2 {
+ continue
+ }
+ g := sourceGroups[k]
+ switch rest {
+ case "UserId":
+ // BUG if the user id is blank, this does not conform to the
+ // way that EC2 handles it - a specified but blank owner id
+ // can cause RevokeSecurityGroupIngress to fail with
+ // "group not found" even if the security group id has been
+ // correctly specified.
+ // By failing here, we ensure that we fail early in this case.
+ if !ownerIdPat.MatchString(val) {
+ fatalf(400, "InvalidUserID.Malformed", "Invalid user ID: %q", val)
+ }
+ g.OwnerId = val
+ case "GroupName":
+ g.Name = val
+ case "GroupId":
+ if !secGroupPat.MatchString(val) {
+ fatalf(400, "InvalidGroupId.Malformed", "Invalid group ID: %q", val)
+ }
+ g.Id = val
+ default:
+ fatalf(400, "UnknownParameter", "unknown parameter %q", name)
+ }
+ sourceGroups[k] = g
+ case strings.HasPrefix(rest, "IpRanges."):
+ var id2 int
+ if x, _ := fmt.Sscanf(rest[len("IpRanges."):], "%d.%s", &id2, &rest); x != 2 {
+ continue
+ }
+ switch rest {
+ case "CidrIp":
+ if !ipPat.MatchString(val) {
+ fatalf(400, "InvalidPermission.Malformed", "Invalid IP range: %q", val)
+ }
+ ec2p.SourceIPs = append(ec2p.SourceIPs, val)
+ default:
+ fatalf(400, "UnknownParameter", "unknown parameter %q", name)
+ }
+ default:
+ fatalf(400, "UnknownParameter", "unknown parameter %q", name)
+ }
+ perms[id1] = ec2p
+ }
+ // Associate each set of source groups with its IPPerm.
+ for k, g := range sourceGroups {
+ p := perms[k.id1]
+ p.SourceGroups = append(p.SourceGroups, g)
+ perms[k.id1] = p
+ }
+
+ // Now that we have built up the IPPerms we need, we check for
+ // parameter errors and build up a permKey for each permission,
+ // looking up security groups from srv as we do so.
+ var result []permKey
+ for _, p := range perms {
+ if p.FromPort > p.ToPort {
+ fatalf(400, "InvalidParameterValue", "invalid port range")
+ }
+ k := permKey{
+ protocol: p.Protocol,
+ fromPort: p.FromPort,
+ toPort: p.ToPort,
+ }
+ for _, g := range p.SourceGroups {
+ if g.OwnerId != "" && g.OwnerId != ownerId {
+ fatalf(400, "InvalidGroup.NotFound", "group %q not found", g.Name)
+ }
+ var ec2g ec2.SecurityGroup
+ switch {
+ case g.Id != "":
+ ec2g.Id = g.Id
+ case g.Name != "":
+ ec2g.Name = g.Name
+ }
+ k.group = srv.group(ec2g)
+ if k.group == nil {
+ fatalf(400, "InvalidGroup.NotFound", "group %v not found", g)
+ }
+ result = append(result, k)
+ }
+ k.group = nil
+ for _, ip := range p.SourceIPs {
+ k.ipAddr = ip
+ result = append(result, k)
+ }
+ }
+ return result
+}
+
+func (srv *Server) deleteSecurityGroup(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
+ srv.mu.Lock()
+ defer srv.mu.Unlock()
+ g := srv.group(ec2.SecurityGroup{
+ Name: req.Form.Get("GroupName"),
+ Id: req.Form.Get("GroupId"),
+ })
+ if g == nil {
+ fatalf(400, "InvalidGroup.NotFound", "group not found")
+ }
+ for _, r := range srv.reservations {
+ for _, h := range r.groups {
+ if h == g && r.hasRunningMachine() {
+ fatalf(500, "InvalidGroup.InUse", "group is currently in use by a running instance")
+ }
+ }
+ }
+ for _, sg := range srv.groups {
+ // If a group refers to itself, it's ok to delete it.
+ if sg == g {
+ continue
+ }
+ for k := range sg.perms {
+ if k.group == g {
+ fatalf(500, "InvalidGroup.InUse", "group is currently in use by group %q", sg.id)
+ }
+ }
+ }
+
+ delete(srv.groups, g.id)
+ return &ec2.SimpleResp{
+ XMLName: xml.Name{"", "DeleteSecurityGroupResponse"},
+ RequestId: reqId,
+ }
+}
+
+func (r *reservation) hasRunningMachine() bool {
+ for _, inst := range r.instances {
+ if inst.state.Code != ShuttingDown.Code && inst.state.Code != Terminated.Code {
+ return true
+ }
+ }
+ return false
+}
+
+type counter int
+
+func (c *counter) next() (i int) {
+ i = int(*c)
+ (*c)++
+ return
+}
+
+// atoi is like strconv.Atoi but is fatal if the
+// string is not well formed.
+func atoi(s string) int {
+ i, err := strconv.Atoi(s)
+ if err != nil {
+ fatalf(400, "InvalidParameterValue", "bad number: %v", err)
+ }
+ return i
+}
+
+func fatalf(statusCode int, code string, f string, a ...interface{}) {
+ panic(&ec2.Error{
+ StatusCode: statusCode,
+ Code: code,
+ Message: fmt.Sprintf(f, a...),
+ })
+}