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
+}