Skip to content

Commit

Permalink
Merge pull request #34 from paninetworks/feature/romana-cidr
Browse files Browse the repository at this point in the history
topology: add support for auto creating romana cidr, if one is not provided.
  • Loading branch information
debedb committed Sep 23, 2016
2 parents 911124c + fe08223 commit 6f104c2
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 21 deletions.
15 changes: 15 additions & 0 deletions agent/handlers.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
// Copyright (c) 2016 Pani Networks
// All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package agent

import (
Expand Down
2 changes: 1 addition & 1 deletion common/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ func (dbStore *DbStore) getConnString() string {
if info.Port == 0 {
portStr = ":3306"
}
// First construct the network part, to log it
// First construct the network part, to log it
connStr = fmt.Sprintf("@tcp(%s%s)/%s?parseTime=true", info.Host, portStr, info.Database)
log.Printf("DB: Connection string: ****:****%s", connStr)
// Now add credentials to connection string
Expand Down
4 changes: 2 additions & 2 deletions ipam/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ func (ipamStore *ipamStore) addEndpoint(endpoint *Endpoint, upToEndpointIpInt ui
// See if there is a formerly allocated IP already that has been released
// (marked "in_use")
sel = "MIN(network_id), ip"
log.Printf("IpamStore: Calling SELECT %s FROM endpoints WHERE %s;", sel, fmt.Sprintf(strings.Replace(filter + "AND in_use = 0", "?", "%s", 3), hostId, tenantId, segId))
row = tx.Model(Endpoint{}).Where(filter + "AND in_use = 0", hostId, tenantId, segId).Select(sel).Row()
log.Printf("IpamStore: Calling SELECT %s FROM endpoints WHERE %s;", sel, fmt.Sprintf(strings.Replace(filter+"AND in_use = 0", "?", "%s", 3), hostId, tenantId, segId))
row = tx.Model(Endpoint{}).Where(filter+"AND in_use = 0", hostId, tenantId, segId).Select(sel).Row()
err = common.GetDbErrors(tx)
if err != nil {
log.Printf("Errors: %v", err)
Expand Down
1 change: 0 additions & 1 deletion policy/policy/policy.REMOVED.git-id

This file was deleted.

11 changes: 7 additions & 4 deletions romana/cmd/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func init() {
}

var hostAddCmd = &cli.Command{
Use: "add [hostname][hostip][romana cidr][(optional)agent port]",
Use: "add [hostname][hostip][(optional)romana cidr][(optional)agent port]",
Short: "Add a new host.",
Long: `Add a new host.`,
RunE: hostAdd,
Expand Down Expand Up @@ -81,14 +81,17 @@ var hostRemoveCmd = &cli.Command{
}

func hostAdd(cmd *cli.Command, args []string) error {
if len(args) < 3 || len(args) > 4 {
if len(args) < 2 || len(args) > 4 {
return util.UsageError(cmd,
fmt.Sprintf("expected 3 or 4 arguments, saw %d: %s", len(args), args))
fmt.Sprintf("expected 2, 3 or 4 arguments, saw %d: %s", len(args), args))
}

hostname := args[0]
hostip := args[1]
romanacidr := args[2]
var romanacidr string
if len(args) >= 3 {
romanacidr = args[2]
}
var agentport uint64
if len(args) == 4 {
var err error
Expand Down
153 changes: 143 additions & 10 deletions topology/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@
package topology

import (
"bytes"
"encoding/binary"
"fmt"
"log"

_ "github.com/go-sql-driver/mysql"
"net"
"strconv"
"strings"

"github.com/romana/core/common"

"strconv"
_ "github.com/go-sql-driver/mysql"
)

var (
BITS_IN_BYTE uint = 8
)

type Tor struct {
Expand Down Expand Up @@ -69,13 +77,138 @@ func (topoStore *topoStore) listHosts() ([]common.Host, error) {
return hosts, nil
}

func (topoStore *topoStore) addHost(host *common.Host) (string, error) {
topoStore.DbStore.Db.NewRecord(*host)
db := topoStore.DbStore.Db.Create(host)
err := common.GetDbErrors(db)
// findFirstAvaiableID finds the first available ID
// for the given list of hostIDs. This is mainly to
// reuse the hostID if the host gets deleted and
// use the same subnet in process, since subnet is
// generated based on id as shown in getNetworkFromID.
func findFirstAvaiableID(arr []uint64) uint64 {
for i := 0; i < len(arr)-1; i++ {
if arr[i+1]-arr[i] > 1 {
return arr[i] + 1
}
}
return arr[len(arr)-1] + 1
}

// getNetworkFromID calculates a subnet equivalent to id
// specified, from the avaiable romana cidr. Currently
// only IPv4 is supported.
func getNetworkFromID(id uint64, hostBits uint, cidr string) (string, error) {
if id == 0 || id > uint64(1<<hostBits) {
return "", fmt.Errorf("error: invalid id passed or max subnets already allocated.")
}

networkBits := strings.Split(cidr, "/")
if len(networkBits) != 2 {
return "", fmt.Errorf("error: parsing romana cidr (%s) failed.", cidr)
}

networkBits[0] = strings.TrimSpace(networkBits[0])
romanaPrefix := net.ParseIP(networkBits[0])
if romanaPrefix == nil {
return "", fmt.Errorf("error: parsing romana cidr (%s) failed.", networkBits[0])
}

networkBits[1] = strings.TrimSpace(networkBits[1])
romanaPrefixBits, err := strconv.ParseUint(networkBits[1], 10, 64)
if err != nil {
return "", fmt.Errorf("error: parsing romana cidr (%s) failed.", networkBits[1])
}

var romanaPrefixUint32 uint32
byteRomanaPrefix := romanaPrefix.To4()
bufRomanaPrefix := bytes.NewReader(byteRomanaPrefix)
err = binary.Read(bufRomanaPrefix, binary.BigEndian, &romanaPrefixUint32)
if err != nil {
return "", fmt.Errorf("error: parsing romana cidr (%s) failed.", romanaPrefix)
}

// since this function is limited to IPv4, handle romanaPrefixBits accordingly.
if hostBits >= (net.IPv4len*BITS_IN_BYTE - uint(romanaPrefixBits)) {
return "", fmt.Errorf("error: invalid number of bits allocated for hosts.")
}

var hostIP uint32
hostIP = (romanaPrefixUint32 >> (net.IPv4len*BITS_IN_BYTE - uint(romanaPrefixBits))) << hostBits
hostIP += uint32(id) - 1
hostIP <<= (net.IPv4len*BITS_IN_BYTE - uint(romanaPrefixBits)) - hostBits

bufHostIP := new(bytes.Buffer)
err = binary.Write(bufHostIP, binary.BigEndian, hostIP)
if err != nil {
return "", fmt.Errorf("error: subnet ip calculation (%s) failed.", err)
}

var hostIPNet net.IPNet
hostIPNet.IP = make(net.IP, net.IPv4len)
hostIPNet.Mask = make(net.IPMask, net.IPv4len)
for i := range hostIPNet.IP {
hostIPNet.IP[i] = bufHostIP.Bytes()[i]
}

var hostMask uint32
for i := uint(0); i < hostBits+uint(romanaPrefixBits); i++ {
hostMask |= 1 << i
}
hostMask <<= (net.IPv4len*BITS_IN_BYTE - uint(romanaPrefixBits)) - hostBits
bufHostMask := new(bytes.Buffer)
err = binary.Write(bufHostMask, binary.BigEndian, hostMask)
if err != nil {
log.Printf("topology.store.addHost(%v): %v", host, err)
return "", err
return "", fmt.Errorf("error: subnet mask calculation (%s) failed.", err)
}
for i := range hostIPNet.Mask {
hostIPNet.Mask[i] = bufHostMask.Bytes()[i]
}

return hostIPNet.String(), nil
}

// addHost adds a new host to a specific datacenter, it also makes sure
// that if a romana cidr is not assigned, then to create and assign a new
// romana cidr using help of helper functions like findFirstAvaiableID
// and getNetworkFromID.
func (topoStore *topoStore) addHost(dc *common.Datacenter, host *common.Host) error {
romanaIP := strings.TrimSpace(host.RomanaIp)
if romanaIP == "" {
var err error
tx := topoStore.DbStore.Db.Begin()

var allHostsID []uint64
if err := tx.Table("hosts").Pluck("id", &allHostsID).Error; err != nil {
tx.Rollback()
return err
}

id := findFirstAvaiableID(allHostsID)
host.RomanaIp, err = getNetworkFromID(id, dc.PortBits, dc.Cidr)
// TODO: auto generation of romana cidr doesn't handle previously
// allocated cidrs currently, thus it needs to be handled
// here so that no 2 hosts get same or overlapping cidrs.
// here check needs to be in place to detect all manually
// inserted romana cidrs for overlap.
if err != nil {
tx.Rollback()
return err
}

if err := tx.Create(host).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
} else {
// TODO: auto generation of romana cidr doesn't handle previously
// allocated cidrs currently, thus it needs to be handled
// here so that no 2 hosts get same or overlapping cidrs.
// here check needs to be in place that auto generated cidrs
// overlap with this manually assigned one or not.
topoStore.DbStore.Db.NewRecord(*host)
db := topoStore.DbStore.Db.Create(host)
if err := common.GetDbErrors(db); err != nil {
log.Printf("topology.store.addHost(%v): %v", host, err)
return err
}
}
return strconv.FormatUint(host.ID, 10), nil
return nil
}
2 changes: 1 addition & 1 deletion topology/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (topology *TopologySvc) handleHostListPost(input interface{}, ctx common.Re
}
}
log.Printf("Host will be added with agent port %d", host.AgentPort)
_, err := topology.store.addHost(host)
err := topology.store.addHost(topology.datacenter, host)
if err != nil {
return nil, err
}
Expand Down
25 changes: 23 additions & 2 deletions topology/topology_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,30 @@ func (s *MySuite) TestTopology(c *check.C) {
c.Assert(newHostResp.Ip, check.Equals, "10.10.10.11")
c.Assert(newHostResp.ID, check.Equals, uint64(2))

newHostReqWithoutRomanaIP := common.Host{Ip: "10.10.10.12", AgentPort: 9999, Name: "host12"}
newHostRespWithoutRomanaIP := common.Host{}
client.Post(hostsRelURL, newHostReqWithoutRomanaIP, &newHostRespWithoutRomanaIP)
myLog(c, "Response: ", newHostRespWithoutRomanaIP)

c.Assert(newHostRespWithoutRomanaIP.Ip, check.Equals, "10.10.10.12")
c.Assert(newHostRespWithoutRomanaIP.RomanaIp, check.Equals, "10.2.0.0/16")
c.Assert(newHostRespWithoutRomanaIP.ID, check.Equals, uint64(3))

newHostReqWithoutRomanaIP = common.Host{Ip: "10.10.10.13", AgentPort: 9999, Name: "host13"}
newHostRespWithoutRomanaIP = common.Host{}
client.Post(hostsRelURL, newHostReqWithoutRomanaIP, &newHostRespWithoutRomanaIP)
myLog(c, "Response: ", newHostRespWithoutRomanaIP)

c.Assert(newHostRespWithoutRomanaIP.Ip, check.Equals, "10.10.10.13")
c.Assert(newHostRespWithoutRomanaIP.RomanaIp, check.Equals, "10.3.0.0/16")
c.Assert(newHostRespWithoutRomanaIP.ID, check.Equals, uint64(4))

// TODO: auto generation of romana cidr currently don't
// handle manually assigned one gracefully, thus tests
// to be added here once that support is added.

var hostList2 []common.Host
client.Get(hostsRelURL, &hostList2)
myLog(c, "Host list: ", hostList2)
c.Assert(len(hostList2), check.Equals, 2)

c.Assert(len(hostList2), check.Equals, 4)
}

0 comments on commit 6f104c2

Please sign in to comment.