Skip to content

Commit

Permalink
initial reservations (#22)
Browse files Browse the repository at this point in the history
* add subnet start/end addresses with ipv4/ipv6 support

* add lease creation and initial reservation logic

* add lease routes and update reservations
  • Loading branch information
jfrey committed Oct 7, 2016
1 parent 5edb214 commit bb9c84b
Show file tree
Hide file tree
Showing 15 changed files with 394 additions and 11 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ release: build

run: release
@docker-compose up

mongo:
@docker exec -it mongodb mongo ipam
32 changes: 32 additions & 0 deletions controllers/controllers_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type MockIpam struct {
ReservationCreated models.Reservation
ReservationUpdated models.Reservation
ReservationDeleted string

Leases []models.Lease
LeaseUpdated models.Lease
}

func NewMockIpam() *MockIpam {
Expand Down Expand Up @@ -241,3 +244,32 @@ func (mock *MockIpam) GetPoolReservations(string) ([]models.Reservation, error)

return mock.Reservations, mock.Err
}

// GetReservations ...
func (mock *MockIpam) GetLeases(string) ([]models.Lease, error) {
if mock.Err != nil {
return []models.Lease{}, mock.Err
}

return mock.Leases, mock.Err
}

// GetReservation ...
func (mock *MockIpam) GetLease(string) (models.Lease, error) {
if mock.Err != nil {
return models.Lease{}, mock.Err
}

return mock.Leases[0], mock.Err
}

// UpdateReservation ...
func (mock *MockIpam) UpdateLease(lease models.Lease) error {
if mock.Err != nil {
return mock.Err
}

mock.LeaseUpdated = lease

return mock.Err
}
79 changes: 79 additions & 0 deletions controllers/leases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package controllers

import (
"fmt"
"net/http"

"gopkg.in/mgo.v2/bson"

"github.com/RackHD/ipam/controllers/helpers"
"github.com/RackHD/ipam/interfaces"
"github.com/RackHD/ipam/models"
"github.com/RackHD/ipam/resources"
"github.com/gorilla/mux"
)

// LeasesController provides methods for handling requests to the Leases API.
type LeasesController struct {
ipam interfaces.Ipam
}

// NewLeasesController returns a newly configured LeasesController.
func NewLeasesController(router *mux.Router, ipam interfaces.Ipam) (*LeasesController, error) {
c := LeasesController{
ipam: ipam,
}

router.Handle("/reservations/{id}/leases", helpers.ErrorHandler(c.Index)).Methods(http.MethodGet)
router.Handle("/leases/{id}", helpers.ErrorHandler(c.Show)).Methods(http.MethodGet)
router.Handle("/leases/{id}", helpers.ErrorHandler(c.Update)).Methods(http.MethodPut, http.MethodPatch)

return &c, nil
}

// Index returns a list of Leases.
func (c *LeasesController) Index(w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)

reservations, err := c.ipam.GetLeases(vars["id"])
if err != nil {
return err
}

return helpers.RenderResource(w, r, resources.LeasesResourceType, http.StatusOK, reservations)
}

// Show returns the requested Lease.
func (c *LeasesController) Show(w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)

reservation, err := c.ipam.GetLease(vars["id"])
if err != nil {
return err
}

return helpers.RenderResource(w, r, resources.LeaseResourceType, http.StatusOK, reservation)
}

// Update updates the requested Lease.
func (c *LeasesController) Update(w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)

resource, err := helpers.AcceptResource(r, resources.LeaseResourceType)
if err != nil {
return err
}

if reservation, ok := resource.(models.Lease); ok {
reservation.ID = bson.ObjectIdHex(vars["id"])

err = c.ipam.UpdateLease(reservation)
if err != nil {
return err
}

return helpers.RenderLocation(w, http.StatusNoContent, fmt.Sprintf("/reservations/%s", reservation.ID.Hex()))
}

return fmt.Errorf("Invalid Resource Type")
}
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ services:
mongodb:
image: "mongo:latest"
container_name: "mongodb"
hostname: "mongodb"
hostname: "mongodb:3.0.12"
expose:
- "27017"

Expand Down
1 change: 1 addition & 0 deletions interfaces/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ type Ipam interface {
Pools
Subnets
Reservations
Leases
}
10 changes: 10 additions & 0 deletions interfaces/leases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package interfaces

import "github.com/RackHD/ipam/models"

// Leases interface defines the methods for implementing Lease related business logic.
type Leases interface {
GetLeases(string) ([]models.Lease, error)
GetLease(string) (models.Lease, error)
UpdateLease(models.Lease) error
}
39 changes: 39 additions & 0 deletions ipam/leases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ipam

import (
"github.com/RackHD/ipam/models"
"gopkg.in/mgo.v2/bson"
)

// IpamCollectionLeases is the name of the Mongo collection which stores Leases.
const IpamCollectionLeases string = "leases"

// GetLeases returns a list of Leases.
func (ipam *Ipam) GetLeases(id string) ([]models.Lease, error) {
session := ipam.session.Copy()
defer session.Close()

var reservations []models.Lease

session.DB(IpamDatabase).C(IpamCollectionLeases).Find(bson.M{"reservation": bson.ObjectIdHex(id)}).All(&reservations)

return reservations, nil
}

// GetLease returns the requested Lease.
func (ipam *Ipam) GetLease(id string) (models.Lease, error) {
session := ipam.session.Copy()
defer session.Close()

var reservation models.Lease

return reservation, session.DB(IpamDatabase).C(IpamCollectionLeases).Find(bson.M{"_id": bson.ObjectIdHex(id)}).One(&reservation)
}

// UpdateLease updates a Lease.
func (ipam *Ipam) UpdateLease(reservation models.Lease) error {
session := ipam.session.Copy()
defer session.Close()

return session.DB(IpamDatabase).C(IpamCollectionLeases).UpdateId(reservation.ID, reservation)
}
10 changes: 9 additions & 1 deletion ipam/reservations.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@ func (ipam *Ipam) CreateReservation(reservation models.Reservation) error {
session := ipam.session.Copy()
defer session.Close()

return session.DB(IpamDatabase).C(IpamCollectionReservations).Insert(reservation)
err := session.DB(IpamDatabase).C(IpamCollectionReservations).Insert(reservation)
if err != nil {
return err
}

return session.DB(IpamDatabase).C(IpamCollectionLeases).Update(
bson.M{"reservation": nil},
bson.M{"$set": bson.M{"reservation": reservation.ID}},
)
}

// UpdateReservation updates a Reservation.
Expand Down
50 changes: 45 additions & 5 deletions ipam/subnets.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package ipam

import (
"encoding/binary"
"fmt"

"github.com/RackHD/ipam/models"
"gopkg.in/mgo.v2/bson"
)
Expand All @@ -15,7 +18,7 @@ func (ipam *Ipam) GetSubnets(id string) ([]models.Subnet, error) {

var subnets []models.Subnet

session.DB(IpamDatabase).C(IpamCollectionSubnets).Find(bson.M{"subnet": bson.ObjectIdHex(id)}).All(&subnets)
session.DB(IpamDatabase).C(IpamCollectionSubnets).Find(bson.M{"pool": bson.ObjectIdHex(id)}).All(&subnets)

return subnets, nil
}
Expand All @@ -35,15 +38,52 @@ func (ipam *Ipam) CreateSubnet(subnet models.Subnet) error {
session := ipam.session.Copy()
defer session.Close()

return session.DB(IpamDatabase).C(IpamCollectionSubnets).Insert(subnet)
err := session.DB(IpamDatabase).C(IpamCollectionSubnets).Insert(subnet)
if err != nil {
return err
}

// Convert byte arrays to integers (IPv4 only).
start := binary.BigEndian.Uint32(subnet.Start.Data[len(subnet.Start.Data)-4:])
end := binary.BigEndian.Uint32(subnet.End.Data[len(subnet.End.Data)-4:])

// Iterate through the range of IP's and insert a record for each.
for ; start < end; start++ {
// IP's are stored as 16 byte arrays and we're only doing IPv4 so prepend
// the net.IP prefix that denotes an IPv4 address.
prefix := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}
address := make([]byte, 4)

binary.BigEndian.PutUint32(address, start)

// Create the lease record, tie it to the subnet.
lease := models.Lease{
ID: bson.NewObjectId(),
Subnet: subnet.ID,
Address: bson.Binary{
Kind: 0,
Data: append(prefix, address...),
},
}

// Insert, though if we fail one we will have a partially populated subnet pool.
err := session.DB(IpamDatabase).C(IpamCollectionLeases).Insert(lease)
if err != nil {
return err
}
}

return nil
}

// UpdateSubnet updates a Subnet.
func (ipam *Ipam) UpdateSubnet(subnet models.Subnet) error {
session := ipam.session.Copy()
defer session.Close()
return fmt.Errorf("UpdateSubnet Temporarily Disabled.")

return session.DB(IpamDatabase).C(IpamCollectionSubnets).UpdateId(subnet.ID, subnet)
// session := ipam.session.Copy()
// defer session.Close()
//
// return session.DB(IpamDatabase).C(IpamCollectionSubnets).UpdateId(subnet.ID, subnet)
}

// DeleteSubnet removes a Subnet.
Expand Down
13 changes: 9 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ func main() {
var mgoDebug = true

if mgoDebug {
mgo.SetDebug(true)
var aLogger *log.Logger
aLogger = log.New(os.Stderr, "", log.LstdFlags)
mgo.SetLogger(aLogger)
mgo.SetDebug(true)
var aLogger *log.Logger
aLogger = log.New(os.Stderr, "", log.LstdFlags)
mgo.SetLogger(aLogger)
}

// Start off with a new mux router.
Expand Down Expand Up @@ -63,6 +63,11 @@ func main() {
log.Fatalf("%s", err)
}

_, err = controllers.NewLeasesController(router, ipam)
if err != nil {
log.Fatalf("%s", err)
}

// Show off request logging middleware.
logged := handlers.LoggingHandler(os.Stdout, router)

Expand Down
14 changes: 14 additions & 0 deletions models/lease.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package models

import "gopkg.in/mgo.v2/bson"

// Lease is a mgo model representing a collection of Subnet resources.
type Lease struct {
ID bson.ObjectId `bson:"_id"`
Name string `bson:"name"`
Tags []string `bson:"tags"`
Metadata interface{} `bson:"metadata"`
Subnet bson.ObjectId `bson:"subnet,omitempty"`
Reservation bson.ObjectId `bson:"reservation,omitempty"`
Address bson.Binary `bson:"address"`
}
2 changes: 2 additions & 0 deletions models/subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ type Subnet struct {
Tags []string `bson:"tags"`
Metadata interface{} `bson:"metadata"`
Pool bson.ObjectId `bson:"pool,omitempty"`
Start bson.Binary `bson:"start"`
End bson.Binary `bson:"end"`
}
Loading

0 comments on commit bb9c84b

Please sign in to comment.