diff --git a/daemon/networkdriver/ipallocator/allocator.go b/daemon/networkdriver/ipallocator/allocator.go index 914df3494..4bcce6517 100644 --- a/daemon/networkdriver/ipallocator/allocator.go +++ b/daemon/networkdriver/ipallocator/allocator.go @@ -7,9 +7,19 @@ import ( "github.com/dotcloud/docker/pkg/collections" "net" "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 ( ErrNoAvailableIPs = errors.New("no available ip addresses on network") @@ -19,7 +29,6 @@ var ( var ( lock = sync.Mutex{} allocatedIPs = networkSet{} - availableIPS = networkSet{} ) // 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) var ( - existing = allocatedIPs[address.String()] - available = availableIPS[address.String()] + allocated = allocatedIPs[address.String()] pos = getPosition(address, ip) ) - existing.Remove(int(pos)) - available.Push(int(pos)) + allocated.Remove(int(pos)) return nil } @@ -82,29 +89,19 @@ func getPosition(address *net.IPNet, ip *net.IP) int32 { func getNextIp(address *net.IPNet) (*net.IP, error) { var ( ownIP = ipToInt(&address.IP) - available = availableIPS[address.String()] allocated = allocatedIPs[address.String()] first, _ = networkdriver.NetworkRange(address) base = ipToInt(&first) size = int(networkdriver.NetworkSize(address.Mask)) 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 ( firstNetIP = address.IP.To4().Mask(address.Mask) firstAsInt = ipToInt(&firstNetIP) + 1 ) - pos = int32(allocated.PullBack()) for i := int32(0); i < max; i++ { pos = pos%max + 1 next := int32(base + pos) @@ -116,6 +113,7 @@ func getNextIp(address *net.IPNet) (*net.IP, error) { if !allocated.Exists(int(pos)) { ip := intToIP(next) allocated.Push(int(pos)) + atomic.StoreInt32(&allocated.last, pos) return ip, nil } } @@ -124,15 +122,14 @@ func getNextIp(address *net.IPNet) (*net.IP, error) { func registerIP(address *net.IPNet, ip *net.IP) error { var ( - existing = allocatedIPs[address.String()] - available = availableIPS[address.String()] + allocated = allocatedIPs[address.String()] pos = getPosition(address, ip) ) - if existing.Exists(int(pos)) { + if allocated.Exists(int(pos)) { return ErrIPAlreadyAllocated } - available.Remove(int(pos)) + atomic.StoreInt32(&allocated.last, pos) return nil } @@ -153,7 +150,6 @@ func intToIP(n int32) *net.IP { func checkAddress(address *net.IPNet) { key := address.String() if _, exists := allocatedIPs[key]; !exists { - allocatedIPs[key] = collections.NewOrderedIntSet() - availableIPS[key] = collections.NewOrderedIntSet() + allocatedIPs[key] = newAllocatedMap() } } diff --git a/daemon/networkdriver/ipallocator/allocator_test.go b/daemon/networkdriver/ipallocator/allocator_test.go index 5e9fcfc98..2b63d9a03 100644 --- a/daemon/networkdriver/ipallocator/allocator_test.go +++ b/daemon/networkdriver/ipallocator/allocator_test.go @@ -8,7 +8,6 @@ import ( func reset() { allocatedIPs = networkSet{} - availableIPS = networkSet{} } func TestRequestNewIps(t *testing.T) { @@ -18,8 +17,10 @@ func TestRequestNewIps(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } + var ip *net.IP + var err error for i := 2; i < 10; i++ { - ip, err := RequestIP(network, nil) + ip, err = RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -28,6 +29,17 @@ func TestRequestNewIps(t *testing.T) { 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) { @@ -64,6 +76,17 @@ func TestGetReleasedIp(t *testing.T) { 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) if err != nil { t.Fatal(err) @@ -185,24 +208,6 @@ func TestIPAllocator(t *testing.T) { 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[3], newIPs[1]) 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) { if !ip1.Equal(*ip2) { t.Fatalf("Expected IP %s, got %s", ip1, ip2)