Merge pull request #5937 from LK4D4/ipallocator_refactoring

Ip allocator refactoring
This commit is contained in:
Michael Crosby 2014-05-27 12:22:36 -07:00
commit bdb5aa4c27
2 changed files with 60 additions and 71 deletions

View file

@ -4,19 +4,18 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"github.com/dotcloud/docker/daemon/networkdriver" "github.com/dotcloud/docker/daemon/networkdriver"
"github.com/dotcloud/docker/pkg/collections"
"net" "net"
"sync" "sync"
"sync/atomic"
) )
// allocatedMap is thread-unsafe set of allocated IP
type allocatedMap struct { type allocatedMap struct {
*collections.OrderedIntSet p map[int32]struct{}
last int32 last int32
} }
func newAllocatedMap() *allocatedMap { func newAllocatedMap() *allocatedMap {
return &allocatedMap{OrderedIntSet: collections.NewOrderedIntSet()} return &allocatedMap{p: make(map[int32]struct{})}
} }
type networkSet map[string]*allocatedMap type networkSet map[string]*allocatedMap
@ -35,70 +34,65 @@ var (
// will return the next available ip if the ip provided is nil. If the // will return the next available ip if the ip provided is nil. If the
// ip provided is not nil it will validate that the provided ip is available // ip provided is not nil it will validate that the provided ip is available
// for use or return an error // for use or return an error
func RequestIP(address *net.IPNet, ip *net.IP) (*net.IP, error) { func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
key := network.String()
checkAddress(address) allocated, ok := allocatedIPs[key]
if !ok {
allocated = newAllocatedMap()
allocatedIPs[key] = allocated
}
if ip == nil { if ip == nil {
next, err := getNextIp(address) return allocated.getNextIP(network)
if err != nil {
return nil, err
}
return next, nil
} }
return allocated.checkIP(network, ip)
if err := registerIP(address, ip); err != nil {
return nil, err
}
return ip, nil
} }
// ReleaseIP adds the provided ip back into the pool of // ReleaseIP adds the provided ip back into the pool of
// available ips to be returned for use. // available ips to be returned for use.
func ReleaseIP(address *net.IPNet, ip *net.IP) error { func ReleaseIP(network *net.IPNet, ip *net.IP) error {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
if allocated, exists := allocatedIPs[network.String()]; exists {
checkAddress(address) pos := getPosition(network, ip)
delete(allocated.p, pos)
var ( }
allocated = allocatedIPs[address.String()]
pos = getPosition(address, ip)
)
allocated.Remove(int(pos))
return nil return nil
} }
// convert the ip into the position in the subnet. Only // convert the ip into the position in the subnet. Only
// position are saved in the set // position are saved in the set
func getPosition(address *net.IPNet, ip *net.IP) int32 { func getPosition(network *net.IPNet, ip *net.IP) int32 {
var ( first, _ := networkdriver.NetworkRange(network)
first, _ = networkdriver.NetworkRange(address) return ipToInt(ip) - ipToInt(&first)
base = ipToInt(&first) }
i = ipToInt(ip)
) func (allocated *allocatedMap) checkIP(network *net.IPNet, ip *net.IP) (*net.IP, error) {
return i - base pos := getPosition(network, ip)
if _, ok := allocated.p[pos]; ok {
return nil, ErrIPAlreadyAllocated
}
allocated.p[pos] = struct{}{}
allocated.last = pos
return ip, nil
} }
// return an available ip if one is currently available. If not, // return an available ip if one is currently available. If not,
// return the next available ip for the nextwork // return the next available ip for the nextwork
func getNextIp(address *net.IPNet) (*net.IP, error) { func (allocated *allocatedMap) getNextIP(network *net.IPNet) (*net.IP, error) {
var ( var (
ownIP = ipToInt(&address.IP) ownIP = ipToInt(&network.IP)
allocated = allocatedIPs[address.String()] first, _ = networkdriver.NetworkRange(network)
first, _ = networkdriver.NetworkRange(address) base = ipToInt(&first)
base = ipToInt(&first) size = int(networkdriver.NetworkSize(network.Mask))
size = int(networkdriver.NetworkSize(address.Mask)) max = int32(size - 2) // size -1 for the broadcast network, -1 for the gateway network
max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address pos = allocated.last
pos = atomic.LoadInt32(&allocated.last)
) )
var ( var (
firstNetIP = address.IP.To4().Mask(address.Mask) firstNetIP = network.IP.To4().Mask(network.Mask)
firstAsInt = ipToInt(&firstNetIP) + 1 firstAsInt = ipToInt(&firstNetIP) + 1
) )
@ -109,31 +103,16 @@ func getNextIp(address *net.IPNet) (*net.IP, error) {
if next == ownIP || next == firstAsInt { if next == ownIP || next == firstAsInt {
continue continue
} }
if _, ok := allocated.p[pos]; ok {
if !allocated.Exists(int(pos)) { continue
ip := intToIP(next)
allocated.Push(int(pos))
atomic.StoreInt32(&allocated.last, pos)
return ip, nil
} }
allocated.p[pos] = struct{}{}
allocated.last = pos
return intToIP(next), nil
} }
return nil, ErrNoAvailableIPs return nil, ErrNoAvailableIPs
} }
func registerIP(address *net.IPNet, ip *net.IP) error {
var (
allocated = allocatedIPs[address.String()]
pos = getPosition(address, ip)
)
if allocated.Exists(int(pos)) {
return ErrIPAlreadyAllocated
}
atomic.StoreInt32(&allocated.last, pos)
return nil
}
// Converts a 4 bytes IP into a 32 bit integer // 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())) return int32(binary.BigEndian.Uint32(ip.To4()))
@ -146,10 +125,3 @@ func intToIP(n int32) *net.IP {
ip := net.IP(b) ip := net.IP(b)
return &ip return &ip
} }
func checkAddress(address *net.IPNet) {
key := address.String()
if _, exists := allocatedIPs[key]; !exists {
allocatedIPs[key] = newAllocatedMap()
}
}

View file

@ -324,3 +324,20 @@ func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) {
t.Fatalf("Expected IP %s, got %s", ip1, ip2) t.Fatalf("Expected IP %s, got %s", ip1, ip2)
} }
} }
func BenchmarkRequestIP(b *testing.B) {
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 253; j++ {
_, err := RequestIP(network, nil)
if err != nil {
b.Fatal(err)
}
}
reset()
}
}