From 13bb3a6fa1d2b53a7fb76aae3f1d09acaa963441 Mon Sep 17 00:00:00 2001 From: John Frey Date: Wed, 5 Oct 2016 12:09:42 -0700 Subject: [PATCH] add lease routes and update reservations --- Makefile | 3 + controllers/controllers_suite_test.go | 32 +++++++++++ controllers/leases.go | 79 +++++++++++++++++++++++++++ interfaces/ipam.go | 1 + interfaces/leases.go | 10 ++++ ipam/leases.go | 35 ++++++++++++ ipam/subnets.go | 6 +- main.go | 13 +++-- resources/lease.go | 78 ++++++++++++++++++++++++++ resources/leases.go | 59 ++++++++++++++++++++ 10 files changed, 310 insertions(+), 6 deletions(-) create mode 100644 controllers/leases.go create mode 100644 interfaces/leases.go create mode 100644 resources/lease.go create mode 100644 resources/leases.go diff --git a/Makefile b/Makefile index 4164df1..9834d5b 100644 --- a/Makefile +++ b/Makefile @@ -51,3 +51,6 @@ release: build run: release @docker-compose up + +mongo: + @docker exec -it mongodb mongo ipam diff --git a/controllers/controllers_suite_test.go b/controllers/controllers_suite_test.go index e2e7059..253fc6e 100644 --- a/controllers/controllers_suite_test.go +++ b/controllers/controllers_suite_test.go @@ -49,6 +49,9 @@ type MockIpam struct { ReservationCreated models.Reservation ReservationUpdated models.Reservation ReservationDeleted string + + Leases []models.Lease + LeaseUpdated models.Lease } func NewMockIpam() *MockIpam { @@ -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 +} diff --git a/controllers/leases.go b/controllers/leases.go new file mode 100644 index 0000000..a0b53e3 --- /dev/null +++ b/controllers/leases.go @@ -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") +} diff --git a/interfaces/ipam.go b/interfaces/ipam.go index 5a9f8e5..f88e13d 100644 --- a/interfaces/ipam.go +++ b/interfaces/ipam.go @@ -7,4 +7,5 @@ type Ipam interface { Pools Subnets Reservations + Leases } diff --git a/interfaces/leases.go b/interfaces/leases.go new file mode 100644 index 0000000..70fb498 --- /dev/null +++ b/interfaces/leases.go @@ -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 +} diff --git a/ipam/leases.go b/ipam/leases.go index b74ba20..fcbb3f3 100644 --- a/ipam/leases.go +++ b/ipam/leases.go @@ -1,4 +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) +} diff --git a/ipam/subnets.go b/ipam/subnets.go index 0a31915..4f9069a 100644 --- a/ipam/subnets.go +++ b/ipam/subnets.go @@ -50,10 +50,12 @@ func (ipam *Ipam) CreateSubnet(subnet models.Subnet) error { // 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 - // 12 empty bytes. - prefix := make([]byte, 12) + // 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(), diff --git a/main.go b/main.go index 68eaaee..871c4ba 100644 --- a/main.go +++ b/main.go @@ -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. @@ -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) diff --git a/resources/lease.go b/resources/lease.go new file mode 100644 index 0000000..825c603 --- /dev/null +++ b/resources/lease.go @@ -0,0 +1,78 @@ +package resources + +import ( + "fmt" + "net" + + "github.com/RackHD/ipam/interfaces" + "github.com/RackHD/ipam/models" + "github.com/RackHD/ipam/resources/factory" + "gopkg.in/mgo.v2/bson" +) + +// LeaseResourceType is the media type assigned to a Lease resource. +const LeaseResourceType string = "application/vnd.ipam.lease" + +// LeaseResourceVersionV1 is the semantic version identifier for the Subnet resource. +const LeaseResourceVersionV1 string = "1.0.0" + +func init() { + factory.Register(LeaseResourceType, LeaseCreator) +} + +// LeaseCreator is a factory function for turning a version string into a Lease resource. +func LeaseCreator(version string) (interfaces.Resource, error) { + return &LeaseV1{}, nil +} + +// LeaseV1 represents the v1.0.0 version of the Lease resource. +type LeaseV1 struct { + ID string `json:"id"` + Name string `json:"name"` + Tags []string `json:"tags"` + Metadata interface{} `json:"metadata"` + Subnet string `json:"subnet"` + Reservation string `json:"reservation"` + Address string `json:"address"` +} + +// Type returns the resource type for use in rendering HTTP response headers. +func (s *LeaseV1) Type() string { + return LeaseResourceType +} + +// Version returns the resource version for use in rendering HTTP response headers. +func (s *LeaseV1) Version() string { + return LeaseResourceVersionV1 +} + +// Marshal converts a models.Lease object into this version of the resource. +func (s *LeaseV1) Marshal(object interface{}) error { + if target, ok := object.(models.Lease); ok { + s.ID = target.ID.Hex() + s.Name = target.Name + s.Tags = target.Tags + s.Metadata = target.Metadata + s.Subnet = target.Subnet.Hex() + s.Reservation = target.Reservation.Hex() + s.Address = net.IP(target.Address.Data).String() + + return nil + } + + return fmt.Errorf("Invalid Object Type: %+v", object) +} + +// Unmarshal converts the resource into a models.Lease object. +func (s *LeaseV1) Unmarshal() (interface{}, error) { + if s.ID == "" { + s.ID = bson.NewObjectId().Hex() + } + + return models.Lease{ + ID: bson.ObjectIdHex(s.ID), + Name: s.Name, + Tags: s.Tags, + Metadata: s.Metadata, + }, nil +} diff --git a/resources/leases.go b/resources/leases.go new file mode 100644 index 0000000..9fee4e2 --- /dev/null +++ b/resources/leases.go @@ -0,0 +1,59 @@ +package resources + +import ( + "fmt" + + "github.com/RackHD/ipam/interfaces" + "github.com/RackHD/ipam/models" + "github.com/RackHD/ipam/resources/factory" +) + +// LeasesResourceType is the media type assigned to a collection of Lease resources. +const LeasesResourceType string = "application/vnd.ipam.leases" + +// LeasesResourceVersionV1 is the semantic version identifier for the Pool resource. +const LeasesResourceVersionV1 string = "1.0.0" + +func init() { + factory.Register(LeasesResourceType, LeasesCreator) +} + +// LeasesCreator is a factory function for turning a version string into a Leases resource. +func LeasesCreator(version string) (interfaces.Resource, error) { + return &LeasesV1{}, nil +} + +// LeasesV1 represents the v1.0.0 version of the Leases resource. +type LeasesV1 struct { + Leases []LeaseV1 `json:"leases"` +} + +// Type returns the resource type for use in rendering HTTP response headers. +func (p *LeasesV1) Type() string { + return LeasesResourceType +} + +// Version returns the resource version for use in rendering HTTP response headers. +func (p *LeasesV1) Version() string { + return LeasesResourceVersionV1 +} + +// Marshal converts an array of models.Lease objects into this version of the resource. +func (p *LeasesV1) Marshal(object interface{}) error { + if subnets, ok := object.([]models.Lease); ok { + p.Leases = make([]LeaseV1, len(subnets)) + + for i := range p.Leases { + p.Leases[i].Marshal(subnets[i]) + } + + return nil + } + + return fmt.Errorf("Invalid Object Type.") +} + +// Unmarshal converts the resource into an array of models.Lease objects. +func (p *LeasesV1) Unmarshal() (interface{}, error) { + return nil, fmt.Errorf("Invalid Action for Resource.") +}