1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
Fix duplicate ip allocation
This commit is contained in:
Guillaume J. Charmes 2014-05-14 13:32:27 -07:00
commit 17a1f470ae
4 changed files with 235 additions and 84 deletions

View file

@ -7,9 +7,19 @@ import (
"github.com/dotcloud/docker/pkg/collections" "github.com/dotcloud/docker/pkg/collections"
"net" "net"
"sync" "sync"
"sync/atomic"
) )
type networkSet map[string]*collections.OrderedIntSet type allocatedMap struct {
*collections.OrderedIntSet
last int32
}
func newAllocatedMap() *allocatedMap {
return &allocatedMap{OrderedIntSet: collections.NewOrderedIntSet()}
}
type networkSet map[string]*allocatedMap
var ( var (
ErrNoAvailableIPs = errors.New("no available ip addresses on network") ErrNoAvailableIPs = errors.New("no available ip addresses on network")
@ -19,7 +29,6 @@ var (
var ( var (
lock = sync.Mutex{} lock = sync.Mutex{}
allocatedIPs = networkSet{} allocatedIPs = networkSet{}
availableIPS = networkSet{}
) )
// RequestIP requests an available ip from the given network. It // RequestIP requests an available ip from the given network. It
@ -55,13 +64,11 @@ func ReleaseIP(address *net.IPNet, ip *net.IP) error {
checkAddress(address) checkAddress(address)
var ( var (
existing = allocatedIPs[address.String()] allocated = allocatedIPs[address.String()]
available = availableIPS[address.String()]
pos = getPosition(address, ip) pos = getPosition(address, ip)
) )
existing.Remove(int(pos)) allocated.Remove(int(pos))
available.Push(int(pos))
return nil return nil
} }
@ -82,29 +89,19 @@ func getPosition(address *net.IPNet, ip *net.IP) int32 {
func getNextIp(address *net.IPNet) (*net.IP, error) { func getNextIp(address *net.IPNet) (*net.IP, error) {
var ( var (
ownIP = ipToInt(&address.IP) ownIP = ipToInt(&address.IP)
available = availableIPS[address.String()]
allocated = allocatedIPs[address.String()] allocated = allocatedIPs[address.String()]
first, _ = networkdriver.NetworkRange(address) first, _ = networkdriver.NetworkRange(address)
base = ipToInt(&first) base = ipToInt(&first)
size = int(networkdriver.NetworkSize(address.Mask)) size = int(networkdriver.NetworkSize(address.Mask))
max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address
pos = int32(available.Pop()) pos = atomic.LoadInt32(&allocated.last)
) )
// We pop and push the position not the ip
if pos != 0 {
ip := intToIP(int32(base + pos))
allocated.Push(int(pos))
return ip, nil
}
var ( var (
firstNetIP = address.IP.To4().Mask(address.Mask) firstNetIP = address.IP.To4().Mask(address.Mask)
firstAsInt = ipToInt(&firstNetIP) + 1 firstAsInt = ipToInt(&firstNetIP) + 1
) )
pos = int32(allocated.PullBack())
for i := int32(0); i < max; i++ { for i := int32(0); i < max; i++ {
pos = pos%max + 1 pos = pos%max + 1
next := int32(base + pos) next := int32(base + pos)
@ -116,6 +113,7 @@ func getNextIp(address *net.IPNet) (*net.IP, error) {
if !allocated.Exists(int(pos)) { if !allocated.Exists(int(pos)) {
ip := intToIP(next) ip := intToIP(next)
allocated.Push(int(pos)) allocated.Push(int(pos))
atomic.StoreInt32(&allocated.last, pos)
return ip, nil return ip, nil
} }
} }
@ -124,15 +122,14 @@ func getNextIp(address *net.IPNet) (*net.IP, error) {
func registerIP(address *net.IPNet, ip *net.IP) error { func registerIP(address *net.IPNet, ip *net.IP) error {
var ( var (
existing = allocatedIPs[address.String()] allocated = allocatedIPs[address.String()]
available = availableIPS[address.String()]
pos = getPosition(address, ip) pos = getPosition(address, ip)
) )
if existing.Exists(int(pos)) { if allocated.Exists(int(pos)) {
return ErrIPAlreadyAllocated return ErrIPAlreadyAllocated
} }
available.Remove(int(pos)) atomic.StoreInt32(&allocated.last, pos)
return nil return nil
} }
@ -153,7 +150,6 @@ func intToIP(n int32) *net.IP {
func checkAddress(address *net.IPNet) { func checkAddress(address *net.IPNet) {
key := address.String() key := address.String()
if _, exists := allocatedIPs[key]; !exists { if _, exists := allocatedIPs[key]; !exists {
allocatedIPs[key] = collections.NewOrderedIntSet() allocatedIPs[key] = newAllocatedMap()
availableIPS[key] = collections.NewOrderedIntSet()
} }
} }

View file

@ -8,7 +8,6 @@ import (
func reset() { func reset() {
allocatedIPs = networkSet{} allocatedIPs = networkSet{}
availableIPS = networkSet{}
} }
func TestRequestNewIps(t *testing.T) { func TestRequestNewIps(t *testing.T) {
@ -18,8 +17,10 @@ func TestRequestNewIps(t *testing.T) {
Mask: []byte{255, 255, 255, 0}, Mask: []byte{255, 255, 255, 0},
} }
var ip *net.IP
var err error
for i := 2; i < 10; i++ { for i := 2; i < 10; i++ {
ip, err := RequestIP(network, nil) ip, err = RequestIP(network, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -28,6 +29,17 @@ func TestRequestNewIps(t *testing.T) {
t.Fatalf("Expected ip %s got %s", expected, ip.String()) t.Fatalf("Expected ip %s got %s", expected, ip.String())
} }
} }
value := intToIP(ipToInt(ip) + 1).String()
if err := ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
ip, err = RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String())
}
} }
func TestReleaseIp(t *testing.T) { func TestReleaseIp(t *testing.T) {
@ -64,6 +76,17 @@ func TestGetReleasedIp(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
for i := 0; i < 252; i++ {
_, err = RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
err = ReleaseIP(network, ip)
if err != nil {
t.Fatal(err)
}
}
ip, err = RequestIP(network, nil) ip, err = RequestIP(network, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -185,24 +208,6 @@ func TestIPAllocator(t *testing.T) {
newIPs[i] = ip newIPs[i] = ip
} }
// Before loop begin
// 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 0
// 2(u) - 3(u) - 4(f) - 5(u) - 6(f)
// ↑
// After i = 1
// 2(u) - 3(u) - 4(f) - 5(u) - 6(u)
// ↑
// After i = 2
// 2(u) - 3(u) - 4(u) - 5(u) - 6(u)
// ↑
// Reordered these because the new set will always return the
// lowest ips first and not in the order that they were released
assertIPEquals(t, &expectedIPs[2], newIPs[0]) assertIPEquals(t, &expectedIPs[2], newIPs[0])
assertIPEquals(t, &expectedIPs[3], newIPs[1]) assertIPEquals(t, &expectedIPs[3], newIPs[1])
assertIPEquals(t, &expectedIPs[4], newIPs[2]) assertIPEquals(t, &expectedIPs[4], newIPs[2])
@ -234,6 +239,86 @@ func TestAllocateFirstIP(t *testing.T) {
} }
} }
func TestAllocateAllIps(t *testing.T) {
defer reset()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
var (
current, first *net.IP
err error
isFirst = true
)
for err == nil {
current, err = RequestIP(network, nil)
if isFirst {
first = current
isFirst = false
}
}
if err != ErrNoAvailableIPs {
t.Fatal(err)
}
if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs {
t.Fatal(err)
}
if err := ReleaseIP(network, first); err != nil {
t.Fatal(err)
}
again, err := RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, first, again)
}
func TestAllocateDifferentSubnets(t *testing.T) {
defer reset()
network1 := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
network2 := &net.IPNet{
IP: []byte{127, 0, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
expectedIPs := []net.IP{
0: net.IPv4(192, 168, 0, 2),
1: net.IPv4(192, 168, 0, 3),
2: net.IPv4(127, 0, 0, 2),
3: net.IPv4(127, 0, 0, 3),
}
ip11, err := RequestIP(network1, nil)
if err != nil {
t.Fatal(err)
}
ip12, err := RequestIP(network1, nil)
if err != nil {
t.Fatal(err)
}
ip21, err := RequestIP(network2, nil)
if err != nil {
t.Fatal(err)
}
ip22, err := RequestIP(network2, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, &expectedIPs[0], ip11)
assertIPEquals(t, &expectedIPs[1], ip12)
assertIPEquals(t, &expectedIPs[2], ip21)
assertIPEquals(t, &expectedIPs[3], ip22)
}
func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) { func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) {
if !ip1.Equal(*ip2) { if !ip1.Equal(*ip2) {
t.Fatalf("Expected IP %s, got %s", ip1, ip2) t.Fatalf("Expected IP %s, got %s", ip1, ip2)

View file

@ -1,12 +1,13 @@
package collections package collections
import ( import (
"sort"
"sync" "sync"
) )
// OrderedIntSet is a thread-safe sorted set and a stack. // OrderedIntSet is a thread-safe sorted set and a stack.
type OrderedIntSet struct { type OrderedIntSet struct {
sync.RWMutex sync.Mutex
set []int set []int
} }
@ -15,29 +16,22 @@ func NewOrderedIntSet() *OrderedIntSet {
return &OrderedIntSet{} return &OrderedIntSet{}
} }
// Push takes a string and adds it to the set. If the elem aready exists, it has no effect. // Push takes an int and adds it to the set. If the elem aready exists, it has no effect.
func (s *OrderedIntSet) Push(elem int) { func (s *OrderedIntSet) Push(elem int) {
s.RLock()
for _, e := range s.set {
if e == elem {
s.RUnlock()
return
}
}
s.RUnlock()
s.Lock() s.Lock()
if len(s.set) == 0 {
// Make sure the list is always sorted s.set = append(s.set, elem)
for i, e := range s.set {
if elem < e {
s.set = append(s.set[:i], append([]int{elem}, s.set[i:]...)...)
s.Unlock() s.Unlock()
return return
} }
// Make sure the list is always sorted
i := sort.SearchInts(s.set, elem)
if i < len(s.set) && s.set[i] == elem {
s.Unlock()
return
} }
// If we reach here, then elem is the biggest elem of the list. s.set = append(s.set[:i], append([]int{elem}, s.set[i:]...)...)
s.set = append(s.set, elem)
s.Unlock() s.Unlock()
} }
@ -46,28 +40,26 @@ func (s *OrderedIntSet) Pop() int {
return s.PopFront() return s.PopFront()
} }
// Pop returns the first elemen from the list and removes it. // Pop returns the first element from the list and removes it.
// If the list is empty, it returns 0 // If the list is empty, it returns 0
func (s *OrderedIntSet) PopFront() int { func (s *OrderedIntSet) PopFront() int {
s.RLock()
for i, e := range s.set {
ret := e
s.RUnlock()
s.Lock() s.Lock()
s.set = append(s.set[:i], s.set[i+1:]...) if len(s.set) == 0 {
s.Unlock()
return 0
}
ret := s.set[0]
s.set = s.set[1:]
s.Unlock() s.Unlock()
return ret return ret
}
s.RUnlock()
return 0
} }
// PullBack retrieve the last element of the list. // PullBack retrieve the last element of the list.
// The element is not removed. // The element is not removed.
// If the list is empty, an empty element is returned. // If the list is empty, an empty element is returned.
func (s *OrderedIntSet) PullBack() int { func (s *OrderedIntSet) PullBack() int {
s.Lock()
defer s.Unlock()
if len(s.set) == 0 { if len(s.set) == 0 {
return 0 return 0
} }
@ -76,21 +68,28 @@ func (s *OrderedIntSet) PullBack() int {
// Exists checks if the given element present in the list. // Exists checks if the given element present in the list.
func (s *OrderedIntSet) Exists(elem int) bool { func (s *OrderedIntSet) Exists(elem int) bool {
for _, e := range s.set { s.Lock()
if e == elem { if len(s.set) == 0 {
return true s.Unlock()
}
}
return false return false
}
i := sort.SearchInts(s.set, elem)
res := i < len(s.set) && s.set[i] == elem
s.Unlock()
return res
} }
// Remove removes an element from the list. // Remove removes an element from the list.
// If the element is not found, it has no effect. // If the element is not found, it has no effect.
func (s *OrderedIntSet) Remove(elem int) { func (s *OrderedIntSet) Remove(elem int) {
for i, e := range s.set { s.Lock()
if e == elem { if len(s.set) == 0 {
s.set = append(s.set[:i], s.set[i+1:]...) s.Unlock()
return return
} }
i := sort.SearchInts(s.set, elem)
if i < len(s.set) && s.set[i] == elem {
s.set = append(s.set[:i], s.set[i+1:]...)
} }
s.Unlock()
} }

View file

@ -0,0 +1,71 @@
package collections
import (
"math/rand"
"testing"
)
func BenchmarkPush(b *testing.B) {
var testSet []int
for i := 0; i < 1000; i++ {
testSet = append(testSet, rand.Int())
}
s := NewOrderedIntSet()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, elem := range testSet {
s.Push(elem)
}
}
}
func BenchmarkPop(b *testing.B) {
var testSet []int
for i := 0; i < 1000; i++ {
testSet = append(testSet, rand.Int())
}
s := NewOrderedIntSet()
for _, elem := range testSet {
s.Push(elem)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
s.Pop()
}
}
}
func BenchmarkExist(b *testing.B) {
var testSet []int
for i := 0; i < 1000; i++ {
testSet = append(testSet, rand.Intn(2000))
}
s := NewOrderedIntSet()
for _, elem := range testSet {
s.Push(elem)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
s.Exists(j)
}
}
}
func BenchmarkRemove(b *testing.B) {
var testSet []int
for i := 0; i < 1000; i++ {
testSet = append(testSet, rand.Intn(2000))
}
s := NewOrderedIntSet()
for _, elem := range testSet {
s.Push(elem)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
s.Remove(j)
}
}
}