From 04df7cfaf8e0bb617244aec828910c75dc12e3c5 Mon Sep 17 00:00:00 2001 From: pritesh Date: Tue, 30 Aug 2016 16:01:51 -0700 Subject: [PATCH 1/7] topology: add support for auto assignment of romana cidr. --- topology/store.go | 94 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/topology/store.go b/topology/store.go index b2f5c9d..506b11e 100644 --- a/topology/store.go +++ b/topology/store.go @@ -17,13 +17,20 @@ package topology import ( + "fmt" "log" - - _ "github.com/go-sql-driver/mysql" + "net" + "strconv" + "strings" "github.com/romana/core/common" - "strconv" + _ "github.com/go-sql-driver/mysql" +) + +var ( + MaxHostID uint = 1 << 8 + RomanaCIDR string = "10.0.0.0/8" ) type Tor struct { @@ -69,13 +76,88 @@ func (topoStore *topoStore) listHosts() ([]common.Host, error) { return hosts, nil } +// findFirstAvaiableID finds the first available ID +// for the given list of hostID's. This is mainly to +// reuse the hostID if the host gets deletes and use +// 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. +func getNetworkFromID(id uint64) (string, error) { + // Currently only IPv4 is supported with romana host bits as 8. + if id == 0 || id > uint64(MaxHostID) { + return "", fmt.Errorf("error: invalid id passed or max subnets already allocated.") + } + _, net, err := net.ParseCIDR(RomanaCIDR) + if err != nil { + return "", err + } + net.IP[1] = byte(id - 1) + net.Mask[1] = 0xff + return net.String(), 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) + var err error + + // TODO: add support for testing datacenter host bits + // for overflow here, currently it can't be + // done because on k8s we don't support dc + // host bits yet, so check for 8 bits for + // now (i.e 255 max hosts). + var count uint + db := topoStore.DbStore.Db.Count(&count) + if db.Error != nil { + return "", db.Error + } + err = common.MakeMultiError(topoStore.DbStore.Db.GetErrors()) if err != nil { log.Printf("topology.store.addHost(%v): %v", host, err) return "", err } + if count >= MaxHostID { + return "", fmt.Errorf("error: max number (%d) host exceeded.", count) + } + + romanaIP := strings.TrimSpace(host.RomanaIp) + if romanaIP == "" { + tx := topoStore.DbStore.Db.Begin() + + var allHostsID []uint64 + if err := tx.Select("id").Find(&allHostsID).Error; err != nil { + tx.Rollback() + return "", err + } + + id := findFirstAvaiableID(allHostsID) + host.RomanaIp, err = getNetworkFromID(id) + if err != nil { + tx.Rollback() + return "", err + } + + if err := tx.Create(host).Error; err != nil { + tx.Rollback() + return "", err + } + tx.Commit() + } else { + topoStore.DbStore.Db.NewRecord(*host) + db := topoStore.DbStore.Db.Create(host) + err := common.GetDbErrors(db) + if err != nil { + log.Printf("topology.store.addHost(%v): %v", host, err) + return "", err + } + } return strconv.FormatUint(host.ID, 10), nil } From 65da243e45d06a61b742fa85d42cd9d67906962e Mon Sep 17 00:00:00 2001 From: pritesh Date: Tue, 30 Aug 2016 16:36:30 -0700 Subject: [PATCH 2/7] romana: add support for making romana cidr optional. --- romana/cmd/host.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/romana/cmd/host.go b/romana/cmd/host.go index 24651c7..1efc070 100644 --- a/romana/cmd/host.go +++ b/romana/cmd/host.go @@ -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, @@ -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 From 054bc93977647148fe5718711dd7a08939743f84 Mon Sep 17 00:00:00 2001 From: pritesh Date: Wed, 31 Aug 2016 12:40:14 -0700 Subject: [PATCH 3/7] topology: add support for flexible host bits during subnet generation. --- topology/store.go | 80 ++++++++++++++++++++++++++------------------ topology/topology.go | 2 +- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/topology/store.go b/topology/store.go index 506b11e..5d8a384 100644 --- a/topology/store.go +++ b/topology/store.go @@ -17,10 +17,11 @@ package topology import ( + "bytes" + "encoding/binary" "fmt" "log" "net" - "strconv" "strings" "github.com/romana/core/common" @@ -28,11 +29,6 @@ import ( _ "github.com/go-sql-driver/mysql" ) -var ( - MaxHostID uint = 1 << 8 - RomanaCIDR string = "10.0.0.0/8" -) - type Tor struct { Id uint64 `sql:"AUTO_INCREMENT"` datacenter *common.Datacenter @@ -92,40 +88,58 @@ func findFirstAvaiableID(arr []uint64) uint64 { // getNetworkFromID calculates a subnet equivalent to id // specified, from the avaiable romana cidr. -func getNetworkFromID(id uint64) (string, error) { - // Currently only IPv4 is supported with romana host bits as 8. - if id == 0 || id > uint64(MaxHostID) { +// Currently only IPv4 is supported. +func getNetworkFromID(id uint64, hostBits uint) (string, error) { + if id == 0 || id > uint64(1<= 24 { + return "", fmt.Errorf("error: invalid number of bits alloacted for hosts.") + } + + var hostIP uint32 = 0x0A << hostBits + hostIP += uint32(id) - 1 + hostIP <<= 24 - hostBits + + bufHostIP := new(bytes.Buffer) + err := binary.Write(bufHostIP, binary.BigEndian, hostIP) if err != nil { - return "", err + return "", fmt.Errorf("error: subnet ip calculation (%s) failed.", err) + } + var hostIPNet net.IPNet + for i := range hostIPNet.IP { + hostIPNet.IP[i] = bufHostIP.Bytes()[i] + } + + var hostMask uint32 + for i := uint(0); i < hostBits+8; i++ { + hostMask |= 1 << i + } + hostMask <<= 24 - hostBits + bufHostMask := new(bytes.Buffer) + err = binary.Write(bufHostMask, binary.BigEndian, hostMask) + if err != nil { + return "", fmt.Errorf("error: subnet mask calculation (%s) failed.", err) + } + for i := range hostIPNet.Mask { + hostIPNet.Mask[i] = bufHostMask.Bytes()[i] } - net.IP[1] = byte(id - 1) - net.Mask[1] = 0xff - return net.String(), nil -} -func (topoStore *topoStore) addHost(host *common.Host) (string, error) { - var err error + return hostIPNet.String(), nil +} - // TODO: add support for testing datacenter host bits - // for overflow here, currently it can't be - // done because on k8s we don't support dc - // host bits yet, so check for 8 bits for - // now (i.e 255 max hosts). +func (topoStore *topoStore) addHost(dc *common.Datacenter, host *common.Host) error { var count uint db := topoStore.DbStore.Db.Count(&count) if db.Error != nil { - return "", db.Error + return db.Error } - err = common.MakeMultiError(topoStore.DbStore.Db.GetErrors()) + err := common.MakeMultiError(topoStore.DbStore.Db.GetErrors()) if err != nil { - log.Printf("topology.store.addHost(%v): %v", host, err) - return "", err + return err } - if count >= MaxHostID { - return "", fmt.Errorf("error: max number (%d) host exceeded.", count) + if count >= 1< Date: Wed, 31 Aug 2016 18:20:23 -0700 Subject: [PATCH 4/7] topology: make getNetworkFromID flexible enough for all cidrs. --- topology/store.go | 85 +++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/topology/store.go b/topology/store.go index 5d8a384..367d2a8 100644 --- a/topology/store.go +++ b/topology/store.go @@ -22,6 +22,7 @@ import ( "fmt" "log" "net" + "strconv" "strings" "github.com/romana/core/common" @@ -29,6 +30,10 @@ import ( _ "github.com/go-sql-driver/mysql" ) +var ( + BITS_IN_BYTE uint = 8 +) + type Tor struct { Id uint64 `sql:"AUTO_INCREMENT"` datacenter *common.Datacenter @@ -73,8 +78,8 @@ func (topoStore *topoStore) listHosts() ([]common.Host, error) { } // findFirstAvaiableID finds the first available ID -// for the given list of hostID's. This is mainly to -// reuse the hostID if the host gets deletes and use +// 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 { @@ -87,35 +92,66 @@ func findFirstAvaiableID(arr []uint64) uint64 { } // getNetworkFromID calculates a subnet equivalent to id -// specified, from the avaiable romana cidr. -// Currently only IPv4 is supported. -func getNetworkFromID(id uint64, hostBits uint) (string, error) { +// 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<= 24 { - return "", fmt.Errorf("error: invalid number of bits alloacted for hosts.") + + 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 = 0x0A << hostBits + var hostIP uint32 + hostIP = (romanaPrefixUint32 >> (net.IPv4len*BITS_IN_BYTE - uint(romanaPrefixBits))) << hostBits hostIP += uint32(id) - 1 - hostIP <<= 24 - hostBits + hostIP <<= (net.IPv4len*BITS_IN_BYTE - uint(romanaPrefixBits)) - hostBits bufHostIP := new(bytes.Buffer) - err := binary.Write(bufHostIP, binary.BigEndian, hostIP) + 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+8; i++ { + for i := uint(0); i < hostBits+uint(romanaPrefixBits); i++ { hostMask |= 1 << i } - hostMask <<= 24 - hostBits + hostMask <<= (net.IPv4len*BITS_IN_BYTE - uint(romanaPrefixBits)) - hostBits bufHostMask := new(bytes.Buffer) err = binary.Write(bufHostMask, binary.BigEndian, hostMask) if err != nil { @@ -128,22 +164,14 @@ func getNetworkFromID(id uint64, hostBits uint) (string, error) { 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 { - var count uint - db := topoStore.DbStore.Db.Count(&count) - if db.Error != nil { - return db.Error - } - err := common.MakeMultiError(topoStore.DbStore.Db.GetErrors()) - if err != nil { - return err - } - if count >= 1< Date: Wed, 31 Aug 2016 21:15:40 -0700 Subject: [PATCH 5/7] policy: remove unneeded file. --- policy/policy/policy.REMOVED.git-id | 1 - 1 file changed, 1 deletion(-) delete mode 100644 policy/policy/policy.REMOVED.git-id diff --git a/policy/policy/policy.REMOVED.git-id b/policy/policy/policy.REMOVED.git-id deleted file mode 100644 index 0a4f47c..0000000 --- a/policy/policy/policy.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -aa27342627e75352af809fda56bc6226a88ccf43 \ No newline at end of file From 2d4ed2d897b2b5fae0d8366ce7bcdc6ce3e826f7 Mon Sep 17 00:00:00 2001 From: pritesh Date: Thu, 1 Sep 2016 13:07:44 -0700 Subject: [PATCH 6/7] topology: add testcases and todos to handle a specific condition later. --- topology/store.go | 12 +++++++++++- topology/topology_test.go | 25 +++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/topology/store.go b/topology/store.go index 367d2a8..ac69089 100644 --- a/topology/store.go +++ b/topology/store.go @@ -175,13 +175,18 @@ func (topoStore *topoStore) addHost(dc *common.Datacenter, host *common.Host) er tx := topoStore.DbStore.Db.Begin() var allHostsID []uint64 - if err := tx.Select("id").Find(&allHostsID).Error; err != nil { + 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 @@ -193,6 +198,11 @@ func (topoStore *topoStore) addHost(dc *common.Datacenter, host *common.Host) er } 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 { diff --git a/topology/topology_test.go b/topology/topology_test.go index e55bb82..645b519 100644 --- a/topology/topology_test.go +++ b/topology/topology_test.go @@ -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) } From fe082236092aced93bc69b81a77e4d8d2f919a01 Mon Sep 17 00:00:00 2001 From: pritesh Date: Tue, 6 Sep 2016 14:14:22 -0700 Subject: [PATCH 7/7] agent: add license in files missing it. --- agent/handlers.go | 15 +++++++++++++++ common/store.go | 2 +- ipam/store.go | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/agent/handlers.go b/agent/handlers.go index 6610bd0..74745de 100644 --- a/agent/handlers.go +++ b/agent/handlers.go @@ -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 ( diff --git a/common/store.go b/common/store.go index e3797ff..0ad3696 100644 --- a/common/store.go +++ b/common/store.go @@ -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 diff --git a/ipam/store.go b/ipam/store.go index 75ecb7c..2fc30f5 100644 --- a/ipam/store.go +++ b/ipam/store.go @@ -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)