initial implementation of IP allocator, only using memory storage
diff --git a/ip-allocator/Dockerfile b/ip-allocator/Dockerfile
new file mode 100644
index 0000000..96d97a2
--- /dev/null
+++ b/ip-allocator/Dockerfile
@@ -0,0 +1,16 @@
+FROM golang:alpine
+
+RUN apk --update add git
+
+WORKDIR /go
+RUN go get github.com/tools/godep
+ADD . /go/src/github.com/ciena/cord-ip-allocator
+
+WORKDIR /go/src/github.com/ciena/cord-ip-allocator
+RUN /go/bin/godep restore
+
+WORKDIR /go
+
+RUN go install github.com/ciena/cord-ip-allocator
+
+ENTRYPOINT ["/go/bin/cord-ip-allocator"]
diff --git a/ip-allocator/Godeps/Godeps.json b/ip-allocator/Godeps/Godeps.json
new file mode 100644
index 0000000..8929877
--- /dev/null
+++ b/ip-allocator/Godeps/Godeps.json
@@ -0,0 +1,25 @@
+{
+	"ImportPath": "_/Users/dbainbri/src/maas/ip-allocator",
+	"GoVersion": "go1.6",
+	"Packages": [
+		"github.com/kelseyhightower/envconfig",
+		"github.com/gorilla/mux"
+	],
+	"Deps": [
+		{
+			"ImportPath": "github.com/gorilla/context",
+			"Comment": "v1.1-4-gaed02d1",
+			"Rev": "aed02d124ae4a0e94fea4541c8effd05bf0c8296"
+		},
+		{
+			"ImportPath": "github.com/gorilla/mux",
+			"Comment": "v1.1-9-gbd09be0",
+			"Rev": "bd09be08ed4377796d312df0a45314e11b8f5dc1"
+		},
+		{
+			"ImportPath": "github.com/kelseyhightower/envconfig",
+			"Comment": "1.1.0-17-g91921eb",
+			"Rev": "91921eb4cf999321cdbeebdba5a03555800d493b"
+		}
+	]
+}
diff --git a/ip-allocator/allocate.go b/ip-allocator/allocate.go
new file mode 100644
index 0000000..11eded4
--- /dev/null
+++ b/ip-allocator/allocate.go
@@ -0,0 +1,42 @@
+package main
+
+func Allocate(store Storage, mac string) (string, error) {
+	// Check to see if an IP address is already allocated and if so just
+	// return that value
+	ip, err := store.Get(mac)
+	if err != nil {
+		return "", err
+	}
+
+	if ip != "" {
+		return ip, nil
+	}
+
+	// This MAC does not already have an IP assigned, so pull then next
+	// one off the available queue and return it
+	ip, err = store.Dequeue()
+	if err != nil {
+		return "", err
+	}
+	err = store.Put(mac, ip)
+	if err != nil {
+		store.Enqueue(ip)
+		return "", err
+	}
+	return ip, nil
+}
+
+func Release(store Storage, mac string) error {
+	ip, err := store.Remove(mac)
+	if err != nil {
+		return err
+	}
+
+	if ip != "" {
+		err = store.Enqueue(ip)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/ip-allocator/allocator.go b/ip-allocator/allocator.go
new file mode 100644
index 0000000..ff474b6
--- /dev/null
+++ b/ip-allocator/allocator.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+	"fmt"
+	"github.com/gorilla/mux"
+	"github.com/kelseyhightower/envconfig"
+	"log"
+	"net/http"
+)
+
+type Config struct {
+	Port         int    `default:"4242"`
+	Listen       string `default:"0.0.0.0"`
+	StartAddress string `default:"10.0.0.2" envconfig:"start_address"`
+	AddressCount uint   `default:"252" envconfig:"address_count"`
+}
+
+type Context struct {
+	storage Storage
+}
+
+func main() {
+	context := &Context{}
+
+	config := Config{}
+	err := envconfig.Process("ALLOCATE", &config)
+	if err != nil {
+		log.Fatalf("[error] Unable to parse configuration options : %s", err)
+	}
+
+	log.Printf(`Configuration:
+	    Listen:       %s
+	    Port:         %d
+	    StartAddress: %s
+	    AddressCount: %d`, config.Listen, config.Port, config.StartAddress, config.AddressCount)
+
+	context.storage = &MemoryStorage{}
+	context.storage.Init(config.StartAddress, config.AddressCount)
+
+	router := mux.NewRouter()
+	router.HandleFunc("/allocations/{mac}", context.ReleaseAllocationHandler).Methods("DELETE")
+	router.HandleFunc("/allocations/{mac}", context.AllocationHandler).Methods("GET")
+	router.HandleFunc("/allocations/", context.ListAllocationsHandler).Methods("GET")
+	router.HandleFunc("/addresses/{ip}", context.FreeAddressHandler).Methods("DELETE")
+	http.Handle("/", router)
+
+	http.ListenAndServe(fmt.Sprintf("%s:%d", config.Listen, config.Port), nil)
+}
diff --git a/ip-allocator/handlers.go b/ip-allocator/handlers.go
new file mode 100644
index 0000000..1cae0ad
--- /dev/null
+++ b/ip-allocator/handlers.go
@@ -0,0 +1,118 @@
+package main
+
+import (
+	"encoding/json"
+	"github.com/gorilla/mux"
+	"net/http"
+)
+
+type ErrorMsg struct {
+	Error string
+}
+
+type AllocationMsg struct {
+	Mac string
+	Ip  string
+}
+
+func (c *Context) release(mac string, w http.ResponseWriter) {
+	err := Release(c.storage, mac)
+	if err != nil {
+		msg := ErrorMsg{
+			Error: err.Error(),
+		}
+		bytes, err := json.Marshal(&msg)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		http.Error(w, string(bytes), http.StatusInternalServerError)
+		return
+	}
+	w.WriteHeader(http.StatusOK)
+}
+
+func (c *Context) ReleaseAllocationHandler(w http.ResponseWriter, r *http.Request) {
+	vars := mux.Vars(r)
+	mac := vars["mac"]
+	c.release(mac, w)
+}
+
+func (c *Context) AllocationHandler(w http.ResponseWriter, r *http.Request) {
+	vars := mux.Vars(r)
+	mac := vars["mac"]
+
+	ip, err := Allocate(c.storage, mac)
+	if err != nil {
+		msg := ErrorMsg{
+			Error: err.Error(),
+		}
+		bytes, err := json.Marshal(&msg)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		http.Error(w, string(bytes), http.StatusInternalServerError)
+		return
+	}
+
+	msg := AllocationMsg{
+		Mac: mac,
+		Ip:  ip,
+	}
+	bytes, err := json.Marshal(&msg)
+	if err != nil {
+		msg := ErrorMsg{
+			Error: err.Error(),
+		}
+		bytes, err := json.Marshal(&msg)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		http.Error(w, string(bytes), http.StatusInternalServerError)
+		return
+	}
+	w.Write(bytes)
+}
+
+func (c *Context) ListAllocationsHandler(w http.ResponseWriter, r *http.Request) {
+	all := c.storage.GetAll()
+
+	list := make([]AllocationMsg, len(all))
+	i := 0
+	for k, v := range all {
+		list[i].Mac = k
+		list[i].Ip = v
+		i += 1
+	}
+
+	bytes, err := json.Marshal(&list)
+	if err != nil {
+		msg := ErrorMsg{
+			Error: err.Error(),
+		}
+		bytes, err := json.Marshal(&msg)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		http.Error(w, string(bytes), http.StatusInternalServerError)
+		return
+	}
+	w.Write(bytes)
+}
+
+func (c *Context) FreeAddressHandler(w http.ResponseWriter, r *http.Request) {
+	vars := mux.Vars(r)
+	ip := vars["ip"]
+
+	all := c.storage.GetAll()
+	for k, v := range all {
+		if v == ip {
+			c.release(k, w)
+			return
+		}
+	}
+	http.Error(w, "", http.StatusNotFound)
+}
diff --git a/ip-allocator/ip.go b/ip-allocator/ip.go
new file mode 100644
index 0000000..29e3500
--- /dev/null
+++ b/ip-allocator/ip.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+type IPv4 uint32
+
+func ParseIP(dot string) (IPv4, error) {
+	var ip IPv4 = 0
+	o := strings.Split(dot, ".")
+	for i := 0; i < 4; i += 1 {
+		b, _ := strconv.Atoi(o[i])
+		ip = ip | (IPv4(byte(b)) << (uint(3-i) * 8))
+	}
+	return ip, nil
+}
+
+func (ip IPv4) Next() (IPv4, error) {
+	return ip + 1, nil
+}
+
+func (ip IPv4) String() string {
+	b := []byte{0, 0, 0, 0}
+	for i := 0; i < 4; i += 1 {
+		m := IPv4(255) << uint((3-i)*8)
+		b[i] = byte(((ip & m) >> uint((3-i)*8)))
+	}
+	return fmt.Sprintf("%d.%d.%d.%d", b[0], b[1], b[2], b[3])
+}
diff --git a/ip-allocator/storage.go b/ip-allocator/storage.go
new file mode 100644
index 0000000..26be353
--- /dev/null
+++ b/ip-allocator/storage.go
@@ -0,0 +1,87 @@
+package main
+
+type Storage interface {
+	Init(start string, count uint) error
+	Get(mac string) (string, error)
+	GetAll() map[string]string
+	Put(mac, ip string) error
+	Remove(mac string) (string, error)
+	Dequeue() (string, error)
+	Enqueue(ip string) error
+}
+
+type MemoryStorage struct {
+	allocated               map[string]IPv4
+	available               []IPv4
+	readIdx, writeIdx, size uint
+}
+
+func (s *MemoryStorage) Init(start string, count uint) error {
+	ip, err := ParseIP(start)
+	if err != nil {
+		return err
+	}
+	s.readIdx = 0
+	s.writeIdx = 0
+	s.size = count
+	s.allocated = make(map[string]IPv4)
+	s.available = make([]IPv4, count)
+	for i := uint(0); i < count; i += 1 {
+		s.available[i] = ip
+		ip, err = ip.Next()
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *MemoryStorage) Get(mac string) (string, error) {
+	ip, ok := s.allocated[mac]
+	if !ok {
+		return "", nil
+	}
+	return ip.String(), nil
+}
+
+func (s *MemoryStorage) GetAll() map[string]string {
+	all := make(map[string]string)
+	for k, v := range s.allocated {
+		all[k] = v.String()
+	}
+	return all
+}
+
+func (s *MemoryStorage) Put(mac, ip string) error {
+	data, err := ParseIP(ip)
+	if err != nil {
+		return err
+	}
+	s.allocated[mac] = data
+	return nil
+}
+
+func (s *MemoryStorage) Remove(mac string) (string, error) {
+	ip, ok := s.allocated[mac]
+	if !ok {
+		return "", nil
+	}
+	delete(s.allocated, mac)
+	return ip.String(), nil
+}
+
+func (s *MemoryStorage) Dequeue() (string, error) {
+	ip := s.available[s.readIdx]
+	s.readIdx = (s.readIdx + 1) % s.size
+	return ip.String(), nil
+}
+
+func (s *MemoryStorage) Enqueue(ip string) error {
+	data, err := ParseIP(ip)
+	if err != nil {
+		return err
+	}
+	s.available[s.writeIdx] = data
+	s.writeIdx = (s.writeIdx + 1) % s.size
+	return nil
+}