Finish implementation and begin working on tests

Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)
This commit is contained in:
Michael Crosby 2014-01-22 18:05:20 -08:00
parent 78b3d89f87
commit 6bc05899aa
3 changed files with 89 additions and 61 deletions

View file

@ -5,7 +5,6 @@ import (
"errors"
"github.com/dotcloud/docker/pkg/netlink"
"net"
"sort"
"sync"
)
@ -20,9 +19,11 @@ var (
ErrNetworkAlreadyAllocated = errors.New("requested network overlaps with existing network")
ErrNetworkAlreadyRegisterd = errors.New("requested network is already registered")
ErrNoAvailableIps = errors.New("no available ips on network")
lock = sync.Mutex{}
allocatedIPs = networkSet{}
availableIPS = networkSet{}
ErrIPAlreadyAllocated = errors.New("ip already allocated")
lock = sync.Mutex{}
allocatedIPs = networkSet{}
availableIPS = networkSet{}
)
func RegisterNetwork(network *net.IPNet) error {
@ -41,12 +42,15 @@ func RegisterNetwork(network *net.IPNet) error {
if err := checkExistingNetworkOverlaps(network); err != nil {
return err
}
allocatedIPs[newIPNet(network)] = iPSet{}
n := newIPNet(network)
allocatedIPs[n] = iPSet{}
availableIPS[n] = iPSet{}
return nil
}
func RequestIP(network *net.IPNet, ip *net.IPAddr) (*net.IPAddr, error) {
func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) {
lock.Lock()
defer lock.Unlock()
@ -58,71 +62,65 @@ func RequestIP(network *net.IPNet, ip *net.IPAddr) (*net.IPAddr, error) {
return next, nil
}
if err := validateIP(network, ip); err != nil {
if err := registerIP(network, ip); err != nil {
return nil, err
}
return ip, nil
}
func ReleaseIP(network *net.IPNet, ip *net.IPAddr) error {
func ReleaseIP(network *net.IPNet, ip *net.IP) error {
lock.Lock()
defer lock.Unlock()
n := newIPNet(network)
existing := allocatedIPs[n]
delete(existing, ip.String())
availableIPS[n][ip.String()] = struct{}{}
i := ipToInt(ip)
existing.Remove(int(i))
available := availableIPS[n]
available.Push(int(i))
return nil
}
func getNextIp(network *net.IPNet) (net.IPAddr, error) {
if available, exists := availableIPS[network]; exists {
return nil, nil
}
func getNextIp(network *net.IPNet) (*net.IP, error) {
var (
firstIP, _ = networkRange(network)
ipNum = ipToInt(firstIP)
ownIP = ipToInt(network.IP)
size = networkSize(network.Mask)
n = newIPNet(network)
allocated = allocatedIPs[n]
pos = int32(1)
max = size - 2 // -1 for the broadcast address, -1 for the gateway address
ip *net.IP
newNum int32
inUse bool
n = newIPNet(network)
available = availableIPS[n]
next = available.Pop()
allocated = allocatedIPs[n]
ownIP = int(ipToInt(&network.IP))
)
// Find first unused IP, give up after one whole round
for attempt := int32(0); attempt < max; attempt++ {
newNum = ipNum + pos
pos = pos%max + 1
// The network's IP is never okay to use
if newNum == ownIP {
if next != 0 {
ip := intToIP(int32(next))
allocated.Push(int(next))
return ip, nil
}
size := int(networkSize(network.Mask))
next = allocated.PullBack() + 1
// size -1 for the broadcast address, -1 for the gateway address
for i := 0; i < size-2; i++ {
if next == ownIP {
next++
continue
}
ip = intToIP(newNum)
if _, inUse = allocated[ip.String()]; !inUse {
// We found an unused IP
break
}
}
ip := intToIP(int32(next))
allocated.Push(next)
if ip == nil {
return nil, ErrNoAvailableIps
return ip, nil
}
allocated[ip.String()] = struct{}{}
return ip, nil
return nil, ErrNoAvailableIps
}
func validateIP(network *net.IPNet, ip *net.IPAddr) error {
func registerIP(network *net.IPNet, ip *net.IP) error {
existing := allocatedIPs[newIPNet(network)]
if existing.Exists(int(ipToInt(ip))) {
return ErrIPAlreadyAllocated
}
return nil
}
func checkRouteOverlaps(networks []netlink.Route, toCheck *net.IPNet) error {
@ -150,7 +148,9 @@ func checkExistingNetworkOverlaps(network *net.IPNet) error {
if newIPNet(network) == existing {
return ErrNetworkAlreadyRegisterd
}
if networkOverlaps(network, existing) {
ex := newNetIPNet(existing)
if networkOverlaps(network, ex) {
return ErrNetworkAlreadyAllocated
}
}
@ -186,15 +186,16 @@ func newNetIPNet(network iPNet) *net.IPNet {
}
// Converts a 4 bytes IP into a 32 bit integer
func ipToInt(ip net.IP) int32 {
func ipToInt(ip *net.IP) int32 {
return int32(binary.BigEndian.Uint32(ip.To4()))
}
// Converts 32 bit integer into a 4 bytes IP address
func intToIP(n int32) net.IP {
func intToIP(n int32) *net.IP {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(n))
return net.IP(b)
ip := net.IP(b)
return &ip
}
// Given a netmask, calculates the number of available hosts

View file

@ -0,0 +1,26 @@
package ipallocator
import (
"net"
"testing"
)
func TestRegisterNetwork(t *testing.T) {
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
if err := RegisterNetwork(network); err != nil {
t.Fatal(err)
}
n := newIPNet(network)
if _, exists := allocatedIPs[n]; !exists {
t.Fatal("IPNet should exist in allocated IPs")
}
if _, exists := availableIPS[n]; !exists {
t.Fatal("IPNet should exist in available IPs")
}
}

View file

@ -8,13 +8,13 @@ import (
// iPSet is a thread-safe sorted set and a stack.
type iPSet struct {
sync.RWMutex
set []int32
set []int
}
// Push takes a string and adds it to the set. If the elem aready exists, it has no effect.
func (s *iPSet) Push(elem int32) {
func (s *iPSet) Push(elem int) {
s.RLock()
for i, e := range s.set {
for _, e := range s.set {
if e == elem {
s.RUnlock()
return
@ -30,13 +30,13 @@ func (s *iPSet) Push(elem int32) {
}
// Pop is an alias to PopFront()
func (s *iPSet) Pop() int32 {
func (s *iPSet) Pop() int {
return s.PopFront()
}
// Pop returns the first elemen from the list and removes it.
// If the list is empty, it returns 0
func (s *iPSet) PopFront() int32 {
func (s *iPSet) PopFront() int {
s.RLock()
for i, e := range s.set {
@ -45,24 +45,25 @@ func (s *iPSet) PopFront() int32 {
s.Lock()
s.set = append(s.set[:i], s.set[i+1:]...)
s.Unlock()
return e
return ret
}
s.RUnlock()
return ""
return 0
}
// PullBack retrieve the last element of the list.
// The element is not removed.
// If the list is empty, an empty element is returned.
func (s *iPSet) PullBack() int32 {
func (s *iPSet) PullBack() int {
if len(s.set) == 0 {
return ""
return 0
}
return s.set[len(s.set)-1]
}
// Exists checks if the given element present in the list.
func (s *iPSet) Exists(elem int32) bool {
func (s *iPSet) Exists(elem int) bool {
for _, e := range s.set {
if e == elem {
return true
@ -73,7 +74,7 @@ func (s *iPSet) Exists(elem int32) bool {
// Remove removes an element from the list.
// If the element is not found, it has no effect.
func (s *iPSet) Remove(elem int32) {
func (s *iPSet) Remove(elem int) {
for i, e := range s.set {
if e == elem {
s.set = append(s.set[:i], s.set[i+1:]...)