Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
2038 lines (1881 sloc) 57.6 KB
//
// goamz - Go packages to interact with the Amazon Web Services.
//
// https://wiki.ubuntu.com/goamz
//
// 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"
"io"
"net"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
"time"
"gopkg.in/amz.v1/ec2"
)
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
attributes map[string][]string // attr name -> values
instances map[string]*Instance // id -> instance
reservations map[string]*reservation // id -> reservation
groups map[string]*securityGroup // id -> group
zones []availabilityZone
vpcs map[string]*vpc // id -> vpc
subnets map[string]*subnet // id -> subnet
ifaces map[string]*iface // id -> iface
attachments map[string]*attachment // id -> attachment
maxId counter
reqId counter
reservationId counter
groupId counter
vpcId counter
dhcpOptsId counter
subnetId counter
ifaceId counter
attachId 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 {
seq int
dnsNameSet bool
// UserData holds the data that was passed to the RunInstances request
// when the instance was started.
UserData []byte
imageId string
reservation *reservation
instType string
availZone string
state ec2.InstanceState
subnetId string
vpcId string
ifaces []ec2.NetworkInterface
}
// 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
vpcId 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
case "vpc-id":
return g.vpcId == value, 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
}
type vpc struct {
ec2.VPC
}
func (v *vpc) matchAttr(attr, value string) (ok bool, err error) {
switch attr {
case "cidr":
return v.CIDRBlock == value, nil
case "state":
return v.State == value, nil
case "vpc-id":
return v.Id == value, nil
case "tag", "tag-key", "tag-value", "dhcp-options-id", "isDefault":
return false, fmt.Errorf("%q filter is not implemented", attr)
}
return false, fmt.Errorf("unknown attribute %q", attr)
}
type subnet struct {
ec2.Subnet
}
func (s *subnet) matchAttr(attr, value string) (ok bool, err error) {
switch attr {
case "cidr":
return s.CIDRBlock == value, nil
case "availability-zone":
return s.AvailZone == value, nil
case "state":
return s.State == value, nil
case "subnet-id":
return s.Id == value, nil
case "vpc-id":
return s.VPCId == value, nil
case "defaultForAz", "default-for-az":
val, err := strconv.ParseBool(value)
if err != nil {
return false, fmt.Errorf("bad flag %q: %s", attr, value)
}
return s.DefaultForAZ == val, nil
case "tag", "tag-key", "tag-value", "available-ip-address-count":
return false, fmt.Errorf("%q filter not implemented", attr)
}
return false, fmt.Errorf("unknown attribute %q", attr)
}
type iface struct {
ec2.NetworkInterface
}
func (i *iface) matchAttr(attr, value string) (ok bool, err error) {
notImplemented := []string{
"addresses.", "association.", "tag", "requester-",
"attachment.", "source-dest-check", "mac-address",
"group-", "description", "private-", "owner-id",
}
switch attr {
case "availability-zone":
return i.AvailZone == value, nil
case "network-interface-id":
return i.Id == value, nil
case "status":
return i.Status == value, nil
case "subnet-id":
return i.SubnetId == value, nil
case "vpc-id":
return i.VPCId == value, nil
default:
for _, item := range notImplemented {
if strings.HasPrefix(attr, item) {
return false, fmt.Errorf("%q filter not implemented", attr)
}
}
}
return false, fmt.Errorf("unknown attribute %q", attr)
}
func (srv *Server) addPrivateIPs(nic *iface, numToAdd int, addrs []string) error {
for _, addr := range addrs {
newIP := ec2.PrivateIP{Address: addr, IsPrimary: false}
nic.PrivateIPs = append(nic.PrivateIPs, newIP)
}
if numToAdd == 0 {
// Nothing more to do.
return nil
}
firstIP := ""
if len(nic.PrivateIPs) > 0 {
firstIP = nic.PrivateIPs[len(nic.PrivateIPs)-1].Address
} else {
// Find the primary IP, if available, otherwise use
// the subnet CIDR to generate a valid IP to use.
firstIP = nic.PrivateIPAddress
if firstIP == "" {
sub := srv.subnets[nic.SubnetId]
if sub == nil {
return fmt.Errorf("NIC %q uses invalid subnet id: %v", nic.Id, nic.SubnetId)
}
netIP, _, err := net.ParseCIDR(sub.CIDRBlock)
if err != nil {
return fmt.Errorf("subnet %q has bad CIDR: %v", sub.Id, err)
}
firstIP = netIP.String()
}
}
ip := net.ParseIP(firstIP)
for i := 0; i < numToAdd; i++ {
ip[len(ip)-1] += 1
newIP := ec2.PrivateIP{Address: ip.String(), IsPrimary: false}
nic.PrivateIPs = append(nic.PrivateIPs, newIP)
}
return nil
}
func (srv *Server) removePrivateIP(nic *iface, addr string) error {
for i, privateIP := range nic.PrivateIPs {
if privateIP.Address == addr {
// Remove it, preserving order.
nic.PrivateIPs = append(nic.PrivateIPs[:i], nic.PrivateIPs[i+1:]...)
return nil
}
}
return fmt.Errorf("NIC %q does not have IP %q to remove", nic.Id, addr)
}
type attachment struct {
ec2.NetworkInterfaceAttachment
}
var actions = map[string]func(*Server, http.ResponseWriter, *http.Request, string) interface{}{
"RunInstances": (*Server).runInstances,
"TerminateInstances": (*Server).terminateInstances,
"DescribeInstances": (*Server).describeInstances,
"CreateSecurityGroup": (*Server).createSecurityGroup,
"DescribeAvailabilityZones": (*Server).describeAvailabilityZones,
"DescribeSecurityGroups": (*Server).describeSecurityGroups,
"DeleteSecurityGroup": (*Server).deleteSecurityGroup,
"AuthorizeSecurityGroupIngress": (*Server).authorizeSecurityGroupIngress,
"RevokeSecurityGroupIngress": (*Server).revokeSecurityGroupIngress,
"CreateVpc": (*Server).createVpc,
"DeleteVpc": (*Server).deleteVpc,
"DescribeVpcs": (*Server).describeVpcs,
"CreateSubnet": (*Server).createSubnet,
"DeleteSubnet": (*Server).deleteSubnet,
"DescribeSubnets": (*Server).describeSubnets,
"CreateNetworkInterface": (*Server).createIFace,
"DeleteNetworkInterface": (*Server).deleteIFace,
"DescribeNetworkInterfaces": (*Server).describeIFaces,
"AttachNetworkInterface": (*Server).attachIFace,
"DetachNetworkInterface": (*Server).detachIFace,
"DescribeAccountAttributes": (*Server).accountAttributes,
"AssignPrivateIpAddresses": (*Server).assignPrivateIP,
"UnassignPrivateIpAddresses": (*Server).unassignPrivateIP,
}
const (
ownerId = "9876"
defaultAvailZone = "us-east-1a"
)
// 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{
attributes: make(map[string][]string),
instances: make(map[string]*Instance),
groups: make(map[string]*securityGroup),
vpcs: make(map[string]*vpc),
subnets: make(map[string]*subnet),
ifaces: make(map[string]*iface),
attachments: make(map[string]*attachment),
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
// Add a default availability zone.
var z availabilityZone
z.Name = defaultAvailZone
z.Region = "us-east-1"
z.State = "available"
srv.zones = []availabilityZone{z}
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()
}
func (srv *Server) SetAvailabilityZones(zones []ec2.AvailabilityZoneInfo) {
srv.mu.Lock()
srv.zones = make([]availabilityZone, len(zones))
for i, z := range zones {
srv.zones[i] = availabilityZone{z}
}
srv.mu.Unlock()
}
// SetInitialAttributes sets the given account attributes on the server.
func (srv *Server) SetInitialAttributes(attrs map[string][]string) {
for attrName, values := range attrs {
srv.attributes[attrName] = values
if attrName == "default-vpc" {
// The default-vpc attribute was provided, so create the
// respective VPCs and their subnets.
for _, vpcId := range values {
srv.vpcs[vpcId] = &vpc{ec2.VPC{
Id: vpcId,
State: "available",
CIDRBlock: "10.0.0.0/16",
DHCPOptionsId: fmt.Sprintf("dopt-%d", srv.dhcpOptsId.next()),
InstanceTenancy: "default",
IsDefault: true,
}}
subnetId := fmt.Sprintf("subnet-%d", srv.subnetId.next())
cidrBlock := "10.10.0.0/20"
availIPs, _ := srv.calcSubnetAvailIPs(cidrBlock)
srv.subnets[subnetId] = &subnet{ec2.Subnet{
Id: subnetId,
VPCId: vpcId,
State: "available",
CIDRBlock: cidrBlock,
AvailZone: "us-east-1b",
AvailableIPCount: availIPs,
DefaultForAZ: true,
}}
}
}
}
}
// 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
}
// parseRunNetworkInterfaces parses any RunNetworkInterface parameters
// passed to RunInstances, and returns them, along with a
// limitToOneInstance flag. The flag is set only when an existing
// network interface id is specified, which according to the API
// limits the number of instances to 1.
func (srv *Server) parseRunNetworkInterfaces(req *http.Request) ([]ec2.RunNetworkInterface, bool) {
ifaces := []ec2.RunNetworkInterface{}
limitToOneInstance := false
for attr, vals := range req.Form {
if !strings.HasPrefix(attr, "NetworkInterface.") {
// Only process network interface params.
continue
}
fields := strings.Split(attr, ".")
if len(fields) < 3 || len(vals) != 1 {
fatalf(400, "InvalidParameterValue", "bad param %q: %v", attr, vals)
}
index := atoi(fields[1])
// Field name format: NetworkInterface.<index>.<fieldName>....
for len(ifaces)-1 < index {
ifaces = append(ifaces, ec2.RunNetworkInterface{})
}
iface := ifaces[index]
fieldName := fields[2]
switch fieldName {
case "NetworkInterfaceId":
// We're using an existing NIC, hence only a single
// instance can be launched.
id := vals[0]
if _, ok := srv.ifaces[id]; !ok {
fatalf(400, "InvalidNetworkInterfaceID.NotFound", "no such nic id %q", id)
}
iface.Id = id
limitToOneInstance = true
case "DeviceIndex":
// This applies both when creating a new NIC and when using an existing one.
iface.DeviceIndex = atoi(vals[0])
case "SubnetId":
// We're creating a new NIC (from here on).
id := vals[0]
if _, ok := srv.subnets[id]; !ok {
fatalf(400, "InvalidSubnetID.NotFound", "no such subnet id %q", id)
}
iface.SubnetId = id
case "Description":
iface.Description = vals[0]
case "DeleteOnTermination":
val, err := strconv.ParseBool(vals[0])
if err != nil {
fatalf(400, "InvalidParameterValue", "bad flag %s: %s", fieldName, vals[0])
}
iface.DeleteOnTermination = val
case "PrivateIpAddress":
privateIP := ec2.PrivateIP{Address: vals[0], IsPrimary: true}
iface.PrivateIPs = append(iface.PrivateIPs, privateIP)
// When a single private IP address is explicitly specified,
// only one instance can be launched according to the API docs.
limitToOneInstance = true
case "SecondaryPrivateIpAddressCount":
iface.SecondaryPrivateIPCount = atoi(vals[0])
case "PrivateIpAddresses":
// ...PrivateIpAddress.<ipIndex>.<subFieldName>: vals[0]
if len(fields) < 4 {
fatalf(400, "InvalidParameterValue", "bad param %q: %v", attr, vals)
}
ipIndex := atoi(fields[3])
for len(iface.PrivateIPs)-1 < ipIndex {
iface.PrivateIPs = append(iface.PrivateIPs, ec2.PrivateIP{})
}
privateIP := iface.PrivateIPs[ipIndex]
subFieldName := fields[4]
switch subFieldName {
case "PrivateIpAddress":
privateIP.Address = vals[0]
case "Primary":
val, err := strconv.ParseBool(vals[0])
if err != nil {
fatalf(400, "InvalidParameterValue", "bad flag %s: %s", subFieldName, vals[0])
}
privateIP.IsPrimary = val
default:
fatalf(400, "InvalidParameterValue", "unknown field %s, subfield %s: %s", fieldName, subFieldName, vals[0])
}
iface.PrivateIPs[ipIndex] = privateIP
case "SecurityGroupId":
// ...SecurityGroupId.<#>: <sgId>
for _, sgId := range vals {
if _, ok := srv.groups[sgId]; !ok {
fatalf(400, "InvalidParameterValue", "no such security group id %q", sgId)
}
iface.SecurityGroupIds = append(iface.SecurityGroupIds, sgId)
}
default:
fatalf(400, "InvalidParameterValue", "unknown field %s: %s", fieldName, vals[0])
}
ifaces[index] = iface
}
return ifaces, limitToOneInstance
}
// getDefaultSubnet returns the first default subnet for the AZ in the
// default VPC (if available).
func (srv *Server) getDefaultSubnet() *subnet {
// We need to get the default VPC id and one of its subnets to use.
defaultVPCId := ""
for _, vpc := range srv.vpcs {
if vpc.IsDefault {
defaultVPCId = vpc.Id
break
}
}
if defaultVPCId == "" {
// No default VPC, so nothing to do.
return nil
}
for _, subnet := range srv.subnets {
if subnet.VPCId == defaultVPCId && subnet.DefaultForAZ {
return subnet
}
}
return nil
}
// addDefaultNIC requests the creation of a default network interface
// for each instance to launch in RunInstances, using the given
// instance subnet, and it's called when no explicit NICs are given.
// It returns the populated RunNetworkInterface slice to add to
// RunInstances params.
func (srv *Server) addDefaultNIC(instSubnet *subnet) []ec2.RunNetworkInterface {
if instSubnet == nil {
// No subnet specified, nothing to do.
return nil
}
ip, ipnet, err := net.ParseCIDR(instSubnet.CIDRBlock)
if err != nil {
panic(fmt.Sprintf("subnet %q has invalid CIDR: %v", instSubnet.Id, err.Error()))
}
// Just pick a valid subnet IP, it doesn't have to be unique
// across instances, as this is a testing server.
ip[len(ip)-1] = 5
if !ipnet.Contains(ip) {
panic(fmt.Sprintf("%q does not contain IP %q", instSubnet.Id, ip))
}
return []ec2.RunNetworkInterface{{
Id: fmt.Sprintf("eni-%d", srv.ifaceId.next()),
DeviceIndex: 0,
Description: "created by ec2test server",
DeleteOnTermination: true,
PrivateIPs: []ec2.PrivateIP{
{Address: ip.String(), IsPrimary: true},
},
}}
}
// createNICsOnRun creates and returns any network interfaces
// specified in ifacesToCreate in the server for the given instance id
// and subnet.
func (srv *Server) createNICsOnRun(instId string, instSubnet *subnet, ifacesToCreate []ec2.RunNetworkInterface) []ec2.NetworkInterface {
if instSubnet == nil {
// No subnet specified, nothing to do.
return nil
}
var createdNICs []ec2.NetworkInterface
for _, ifaceToCreate := range ifacesToCreate {
nicId := ifaceToCreate.Id
macAddress := fmt.Sprintf("20:%02x:60:cb:27:37", srv.ifaceId)
if nicId == "" {
// Simulate a NIC got created.
nicId = fmt.Sprintf("eni-%d", srv.ifaceId.next())
macAddress = fmt.Sprintf("20:%02x:60:cb:27:37", srv.ifaceId)
}
groups := make([]ec2.SecurityGroup, len(ifaceToCreate.SecurityGroupIds))
for i, sgId := range ifaceToCreate.SecurityGroupIds {
groups[i] = srv.group(ec2.SecurityGroup{Id: sgId}).ec2SecurityGroup()
}
// Find the primary private IP address for the NIC
// inside the PrivateIPs slice.
primaryIP := ""
privateDNSName := fmt.Sprintf("%s.internal.invalid", instId)
for i, ip := range ifaceToCreate.PrivateIPs {
if ip.IsPrimary {
primaryIP = ip.Address
ifaceToCreate.PrivateIPs[i].DNSName = privateDNSName
break
}
}
attach := ec2.NetworkInterfaceAttachment{
Id: fmt.Sprintf("eni-attach-%d", srv.attachId.next()),
InstanceId: instId,
InstanceOwnerId: ownerId,
DeviceIndex: ifaceToCreate.DeviceIndex,
Status: "in-use",
AttachTime: time.Now().Format(time.RFC3339),
DeleteOnTermination: true,
}
srv.attachments[attach.Id] = &attachment{attach}
nic := ec2.NetworkInterface{
Id: nicId,
SubnetId: instSubnet.Id,
VPCId: instSubnet.VPCId,
AvailZone: instSubnet.AvailZone,
Description: ifaceToCreate.Description,
OwnerId: ownerId,
Status: "in-use",
MACAddress: macAddress,
PrivateIPAddress: primaryIP,
PrivateDNSName: privateDNSName,
SourceDestCheck: true,
Groups: groups,
PrivateIPs: ifaceToCreate.PrivateIPs,
Attachment: attach,
}
srv.ifaces[nicId] = &iface{nic}
createdNICs = append(createdNICs, nic)
}
return createdNICs
}
// 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 ?
// AvailZone ?
// GroupName tag
// Monitoring ignore?
// 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")
availZone := req.Form.Get("Placement.AvailabilityZone")
r := srv.newReservation(srv.formToGroups(req.Form))
// If the user specifies an explicit subnet id, use it.
// Otherwise, get a subnet from the default VPC.
userSubnetId := req.Form.Get("SubnetId")
instSubnet := srv.subnets[userSubnetId]
if instSubnet == nil && userSubnetId != "" {
fatalf(400, "InvalidSubnetID.NotFound", "subnet %s not found", userSubnetId)
}
if userSubnetId == "" {
instSubnet = srv.getDefaultSubnet()
}
// Handle network interfaces parsing.
ifacesToCreate, limitToOneInstance := srv.parseRunNetworkInterfaces(req)
if len(ifacesToCreate) > 0 && userSubnetId != "" {
// Since we have an instance-level subnet id
// specified, we cannot add network interfaces
// in the same request. See http://goo.gl/9aqbT9.
fatalf(400, "InvalidParameterCombination", "Network interfaces and an instance-level subnet ID may not be specified on the same request")
}
if limitToOneInstance {
max = 1
}
if len(ifacesToCreate) == 0 {
// No NICs specified, so create a default one to simulate what
// EC2 does.
ifacesToCreate = srv.addDefaultNIC(instSubnet)
}
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, availZone, srv.initialInstanceState)
// Create any NICs on the instance subnet (if any), and then
// save the VPC and subnet ids on the instance, as EC2 does.
inst.ifaces = srv.createNICsOnRun(inst.id(), instSubnet, ifacesToCreate)
if instSubnet != nil {
inst.subnetId = instSubnet.Id
inst.vpcId = instSubnet.VPCId
}
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
}
// NewInstancesVPC creates n new VPC instances in srv with the given
// instance type, image ID, initial state, and security groups,
// belonging to the given vpcId and subnetId. If any group does not
// already exist, it will be created. NewInstancesVPC returns the ids
// of the new instances.
//
// If vpcId and subnetId are both empty, this call is equivalent to
// calling NewInstances.
func (srv *Server) NewInstancesVPC(vpcId, subnetId string, 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, defaultAvailZone, state)
inst.vpcId = vpcId
inst.subnetId = subnetId
ids[i] = inst.id()
}
return ids
}
// 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 {
return srv.NewInstancesVPC("", "", n, instType, imageId, state, groups)
}
func (srv *Server) newInstance(r *reservation, instType string, imageId string, availZone string, state ec2.InstanceState) *Instance {
inst := &Instance{
seq: srv.maxId.next(),
instType: instType,
imageId: imageId,
availZone: availZone,
state: state,
reservation: r,
}
id := inst.id()
srv.instances[id] = inst
r.instances[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) id() string {
return fmt.Sprintf("i-%d", inst.seq)
}
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 {
id := inst.id()
// The first time the instance is returned, its DNSName
// will be empty. The client should then refresh the instance.
var dnsName string
if inst.dnsNameSet {
dnsName = fmt.Sprintf("%s.testing.invalid", id)
} else {
inst.dnsNameSet = true
}
return ec2.Instance{
InstanceId: id,
InstanceType: inst.instType,
ImageId: inst.imageId,
DNSName: dnsName,
PrivateDNSName: fmt.Sprintf("%s.internal.invalid", id),
IPAddress: fmt.Sprintf("8.0.0.%d", inst.seq%256),
PrivateIPAddress: fmt.Sprintf("127.0.0.%d", inst.seq%256),
State: inst.state,
AvailZone: inst.availZone,
VPCId: inst.vpcId,
SubnetId: inst.subnetId,
NetworkInterfaces: inst.ifaces,
// TODO the rest
}
}
func (inst *Instance) matchAttr(attr, value string) (ok bool, err error) {
switch attr {
case "architecture":
return value == "i386", nil
case "availability-zone":
return value == inst.availZone, nil
case "instance-id":
return inst.id() == value, nil
case "subnet-id":
return inst.subnetId == value, nil
case "vpc-id":
return inst.vpcId == value, nil
case "instance.group-id", "group-id":
for _, g := range inst.reservation.groups {
if g.id == value {
return true, nil
}
}
return false, nil
case "instance.group-name", "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),
}
vpcId := req.Form.Get("VpcId")
if vpcId != "" {
g.vpcId = vpcId
}
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.InstancesResp
resp.RequestId = reqId
for _, r := range srv.reservations {
var instances []ec2.Instance
var groups []ec2.SecurityGroup
for _, g := range r.groups {
groups = append(groups, g.ec2SecurityGroup())
}
for _, inst := range r.instances {
if len(insts) > 0 && !insts[inst] {
continue
}
// make instances in state "shutting-down" to transition
// to "terminated" first, so we can simulate: shutdown,
// subsequent refresh of the state with Instances(),
// terminated.
if inst.state == ShuttingDown {
inst.state = Terminated
}
ok, err := f.ok(inst)
if ok {
instance := inst.ec2instance()
instance.SecurityGroups = groups
instances = append(instances, instance)
} else if err != nil {
fatalf(400, "InvalidParameterValue", "describe instances: %v", err)
}
}
if len(instances) > 0 {
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]+$`)
cidrIpPat = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/([0-9]+)$`)
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 !cidrIpPat.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,
}
}
type availabilityZone struct {
ec2.AvailabilityZoneInfo
}
func (z *availabilityZone) matchAttr(attr, value string) (ok bool, err error) {
switch attr {
case "message":
for _, m := range z.MessageSet {
if m == value {
return true, nil
}
}
return false, nil
case "region-name":
return z.Region == value, nil
case "state":
switch value {
case "available", "impaired", "unavailable":
return z.State == value, nil
}
return false, fmt.Errorf("invalid state %q", value)
case "zone-name":
return z.Name == value, nil
}
return false, fmt.Errorf("unknown attribute %q", attr)
}
func (srv *Server) describeAvailabilityZones(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
srv.mu.Lock()
defer srv.mu.Unlock()
f := newFilter(req.Form)
var resp ec2.AvailabilityZonesResp
resp.RequestId = reqId
for _, zone := range srv.zones {
ok, err := f.ok(&zone)
if ok {
resp.Zones = append(resp.Zones, zone.AvailabilityZoneInfo)
} else if err != nil {
fatalf(400, "InvalidParameterValue", "describe availability zones: %v", err)
}
}
return &resp
}
func (srv *Server) createVpc(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
cidrBlock := parseCidr(req.Form.Get("CidrBlock"))
tenancy := req.Form.Get("InstanceTenancy")
if tenancy == "" {
tenancy = "default"
}
srv.mu.Lock()
defer srv.mu.Unlock()
v := &vpc{ec2.VPC{
Id: fmt.Sprintf("vpc-%d", srv.vpcId.next()),
State: "available",
CIDRBlock: cidrBlock,
DHCPOptionsId: fmt.Sprintf("dopt-%d", srv.dhcpOptsId.next()),
InstanceTenancy: tenancy,
}}
srv.vpcs[v.Id] = v
r := &ec2.CreateVPCResp{
RequestId: reqId,
VPC: v.VPC,
}
return r
}
func (srv *Server) deleteVpc(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
v := srv.vpc(req.Form.Get("VpcId"))
srv.mu.Lock()
defer srv.mu.Unlock()
delete(srv.vpcs, v.Id)
return &ec2.SimpleResp{
XMLName: xml.Name{"", "DeleteVpcResponse"},
RequestId: reqId,
}
}
func (srv *Server) describeVpcs(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
srv.mu.Lock()
defer srv.mu.Unlock()
idMap := parseIDs(req.Form, "VpcId.")
f := newFilter(req.Form)
var resp ec2.VPCsResp
resp.RequestId = reqId
for _, v := range srv.vpcs {
ok, err := f.ok(v)
_, known := idMap[v.Id]
if ok && (len(idMap) == 0 || known) {
resp.VPCs = append(resp.VPCs, v.VPC)
} else if err != nil {
fatalf(400, "InvalidParameterValue", "describe VPCs: %v", err)
}
}
return &resp
}
func (srv *Server) calcSubnetAvailIPs(cidrBlock string) (int, error) {
_, ipnet, err := net.ParseCIDR(cidrBlock)
if err != nil {
return 0, err
}
// calculate the available IP addresses, removing the first 4 and
// the last, which are reserved by AWS.
maskOnes, maskBits := ipnet.Mask.Size()
return 1<<uint(maskBits-maskOnes) - 5, nil
}
func (srv *Server) createSubnet(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
v := srv.vpc(req.Form.Get("VpcId"))
cidrBlock := parseCidr(req.Form.Get("CidrBlock"))
availZone := req.Form.Get("AvailabilityZone")
if availZone == "" {
// Assign one automatically as AWS does.
availZone = "us-east-1b"
}
availIPs, err := srv.calcSubnetAvailIPs(cidrBlock)
if err != nil {
fatalf(400, "InvalidParameterValue", "calcSubnetAvailIPs(%q) failed: %v", cidrBlock, err)
}
srv.mu.Lock()
defer srv.mu.Unlock()
s := &subnet{ec2.Subnet{
Id: fmt.Sprintf("subnet-%d", srv.subnetId.next()),
VPCId: v.Id,
State: "available",
CIDRBlock: cidrBlock,
AvailZone: availZone,
AvailableIPCount: availIPs,
}}
srv.subnets[s.Id] = s
r := &ec2.CreateSubnetResp{
RequestId: reqId,
Subnet: s.Subnet,
}
return r
}
func (srv *Server) deleteSubnet(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
s := srv.subnet(req.Form.Get("SubnetId"))
srv.mu.Lock()
defer srv.mu.Unlock()
delete(srv.subnets, s.Id)
return &ec2.SimpleResp{
XMLName: xml.Name{"", "DeleteSubnetResponse"},
RequestId: reqId,
}
}
func (srv *Server) describeSubnets(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
srv.mu.Lock()
defer srv.mu.Unlock()
idMap := parseIDs(req.Form, "SubnetId.")
f := newFilter(req.Form)
var resp ec2.SubnetsResp
resp.RequestId = reqId
for _, s := range srv.subnets {
ok, err := f.ok(s)
_, known := idMap[s.Id]
if ok && (len(idMap) == 0 || known) {
resp.Subnets = append(resp.Subnets, s.Subnet)
} else if err != nil {
fatalf(400, "InvalidParameterValue", "describe subnets: %v", err)
}
}
return &resp
}
func (srv *Server) createIFace(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
s := srv.subnet(req.Form.Get("SubnetId"))
ipMap := make(map[int]ec2.PrivateIP)
primaryIP := req.Form.Get("PrivateIpAddress")
if primaryIP != "" {
ipMap[0] = ec2.PrivateIP{Address: primaryIP, IsPrimary: true}
}
desc := req.Form.Get("Description")
var groups []ec2.SecurityGroup
for name, vals := range req.Form {
if strings.HasPrefix(name, "SecurityGroupId.") {
g := ec2.SecurityGroup{Id: vals[0]}
sg := srv.group(g)
if sg == nil {
fatalf(400, "InvalidGroup.NotFound", "no such group %v", g)
}
groups = append(groups, sg.ec2SecurityGroup())
}
if strings.HasPrefix(name, "PrivateIpAddresses.") {
var ip ec2.PrivateIP
parts := strings.Split(name, ".")
index := atoi(parts[1]) - 1
if index < 0 {
fatalf(400, "InvalidParameterValue", "invalid index %s", name)
}
if _, ok := ipMap[index]; ok {
ip = ipMap[index]
}
switch parts[2] {
case "PrivateIpAddress":
ip.Address = vals[0]
case "Primary":
val, err := strconv.ParseBool(vals[0])
if err != nil {
fatalf(400, "InvalidParameterValue", "bad flag %s: %s", name, vals[0])
}
ip.IsPrimary = val
}
ipMap[index] = ip
}
}
privateIPs := make([]ec2.PrivateIP, len(ipMap))
for index, ip := range ipMap {
if ip.IsPrimary {
primaryIP = ip.Address
}
privateIPs[index] = ip
}
srv.mu.Lock()
defer srv.mu.Unlock()
i := &iface{ec2.NetworkInterface{
Id: fmt.Sprintf("eni-%d", srv.ifaceId.next()),
SubnetId: s.Id,
VPCId: s.VPCId,
AvailZone: s.AvailZone,
Description: desc,
OwnerId: ownerId,
Status: "available",
MACAddress: fmt.Sprintf("20:%02x:60:cb:27:37", srv.ifaceId),
PrivateIPAddress: primaryIP,
SourceDestCheck: true,
Groups: groups,
PrivateIPs: privateIPs,
}}
srv.ifaces[i.Id] = i
r := &ec2.CreateNetworkInterfaceResp{
RequestId: reqId,
NetworkInterface: i.NetworkInterface,
}
return r
}
func (srv *Server) deleteIFace(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
i := srv.iface(req.Form.Get("NetworkInterfaceId"))
srv.mu.Lock()
defer srv.mu.Unlock()
delete(srv.ifaces, i.Id)
return &ec2.SimpleResp{
XMLName: xml.Name{"", "DeleteNetworkInterface"},
RequestId: reqId,
}
}
func (srv *Server) describeIFaces(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
srv.mu.Lock()
defer srv.mu.Unlock()
idMap := parseIDs(req.Form, "NetworkInterfaceId.")
f := newFilter(req.Form)
var resp ec2.NetworkInterfacesResp
resp.RequestId = reqId
for _, i := range srv.ifaces {
ok, err := f.ok(i)
_, known := idMap[i.Id]
if ok && (len(idMap) == 0 || known) {
resp.Interfaces = append(resp.Interfaces, i.NetworkInterface)
} else if err != nil {
fatalf(400, "InvalidParameterValue", "describe ifaces: %v", err)
}
}
return &resp
}
func (srv *Server) attachIFace(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
i := srv.iface(req.Form.Get("NetworkInterfaceId"))
inst := srv.instance(req.Form.Get("InstanceId"))
devIndex := atoi(req.Form.Get("DeviceIndex"))
srv.mu.Lock()
defer srv.mu.Unlock()
a := &attachment{ec2.NetworkInterfaceAttachment{
Id: fmt.Sprintf("eni-attach-%d", srv.attachId.next()),
InstanceId: inst.id(),
InstanceOwnerId: ownerId,
DeviceIndex: devIndex,
Status: "in-use",
AttachTime: time.Now().Format(time.RFC3339),
DeleteOnTermination: true,
}}
srv.attachments[a.Id] = a
i.Attachment = a.NetworkInterfaceAttachment
srv.ifaces[i.Id] = i
r := &ec2.AttachNetworkInterfaceResp{
RequestId: reqId,
AttachmentId: a.Id,
}
return r
}
func (srv *Server) detachIFace(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
att := srv.attachment(req.Form.Get("AttachmentId"))
srv.mu.Lock()
defer srv.mu.Unlock()
for _, i := range srv.ifaces {
if i.Attachment.Id == att.Id {
i.Attachment = ec2.NetworkInterfaceAttachment{}
srv.ifaces[i.Id] = i
break
}
}
delete(srv.attachments, att.Id)
return &ec2.SimpleResp{
XMLName: xml.Name{"", "DetachNetworkInterface"},
RequestId: reqId,
}
}
func (srv *Server) accountAttributes(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
srv.mu.Lock()
defer srv.mu.Unlock()
attrsMap := parseIDs(req.Form, "AttributeName.")
var resp ec2.AccountAttributesResp
resp.RequestId = reqId
for attrName, _ := range attrsMap {
vals, ok := srv.attributes[attrName]
if !ok {
fatalf(400, "InvalidParameterValue", "describe attrs: not found %q", attrName)
}
resp.Attributes = append(resp.Attributes, ec2.AccountAttribute{attrName, vals})
}
return &resp
}
func (srv *Server) assignPrivateIP(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
nic := srv.iface(req.Form.Get("NetworkInterfaceId"))
extraIPs := parseInOrder(req.Form, "PrivateIpAddress.")
count := req.Form.Get("SecondaryPrivateIpAddressCount")
secondaryIPs := 0
if count != "" {
secondaryIPs = atoi(count)
}
srv.mu.Lock()
defer srv.mu.Unlock()
err := srv.addPrivateIPs(nic, secondaryIPs, extraIPs)
if err != nil {
fatalf(400, "InvalidParameterValue", err.Error())
}
return &ec2.SimpleResp{
XMLName: xml.Name{"", "AssignPrivateIpAddresses"},
RequestId: reqId,
}
}
func (srv *Server) unassignPrivateIP(w http.ResponseWriter, req *http.Request, reqId string) interface{} {
nic := srv.iface(req.Form.Get("NetworkInterfaceId"))
ips := parseInOrder(req.Form, "PrivateIpAddress.")
srv.mu.Lock()
defer srv.mu.Unlock()
for _, ip := range ips {
if err := srv.removePrivateIP(nic, ip); err != nil {
fatalf(400, "InvalidParameterValue", err.Error())
}
}
return &ec2.SimpleResp{
XMLName: xml.Name{"", "UnassignPrivateIpAddresses"},
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
}
func parseCidr(val string) string {
if val == "" {
fatalf(400, "MissingParameter", "missing cidrBlock")
}
if _, _, err := net.ParseCIDR(val); err != nil {
fatalf(400, "InvalidParameterValue", "bad CIDR %q: %v", val, err)
}
return val
}
func (srv *Server) vpc(id string) *vpc {
if id == "" {
fatalf(400, "MissingParameter", "missing vpcId")
}
srv.mu.Lock()
defer srv.mu.Unlock()
v, found := srv.vpcs[id]
if !found {
fatalf(400, "InvalidVpcID.NotFound", "VPC %s not found", id)
}
return v
}
func (srv *Server) subnet(id string) *subnet {
if id == "" {
fatalf(400, "MissingParameter", "missing subnetId")
}
srv.mu.Lock()
defer srv.mu.Unlock()
s, found := srv.subnets[id]
if !found {
fatalf(400, "InvalidSubnetID.NotFound", "subnet %s not found", id)
}
return s
}
func (srv *Server) iface(id string) *iface {
if id == "" {
fatalf(400, "MissingParameter", "missing networkInterfaceId")
}
srv.mu.Lock()
defer srv.mu.Unlock()
i, found := srv.ifaces[id]
if !found {
fatalf(400, "InvalidNetworkInterfaceID.NotFound", "interface %s not found", id)
}
return i
}
func (srv *Server) instance(id string) *Instance {
if id == "" {
fatalf(400, "MissingParameter", "missing instanceId")
}
srv.mu.Lock()
defer srv.mu.Unlock()
inst, found := srv.instances[id]
if !found {
fatalf(400, "InvalidInstanceID.NotFound", "instance %s not found", id)
}
return inst
}
func (srv *Server) attachment(id string) *attachment {
if id == "" {
fatalf(400, "MissingParameter", "missing attachmentId")
}
srv.mu.Lock()
defer srv.mu.Unlock()
att, found := srv.attachments[id]
if !found {
fatalf(
400,
"InvalidNetworkInterfaceAttachmentId.NotFound",
"attachment %s not found", id,
)
}
return att
}
// parseIDs takes all form fields with the given prefix and returns a
// map with their values as keys.
func parseIDs(form url.Values, prefix string) map[string]bool {
idMap := make(map[string]bool)
for field, allValues := range form {
value := allValues[0]
if strings.HasPrefix(field, prefix) && !idMap[value] {
idMap[value] = true
}
}
return idMap
}
// parseInOrder takes all form fields with the given prefix and
// returns their values in request order. Suitable for AWS API
// parameters defining lists, e.g. with fields like
// "PrivateIpAddress.0": v1, "PrivateIpAddress.1" v2, it returns
// []string{v1, v2}.
func parseInOrder(form url.Values, prefix string) []string {
idMap := parseIDs(form, prefix)
results := make([]string, len(idMap))
for field, allValues := range form {
if !strings.HasPrefix(field, prefix) {
continue
}
value := allValues[0]
parts := strings.Split(field, ".")
if len(parts) != 2 {
panic(fmt.Sprintf("expected indexed key %q", field))
}
index := atoi(parts[1])
if idMap[value] {
results[index] = value
}
}
return results
}
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...),
})
}