1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/libnetwork/ipam/allocator_test.go
selansen 52e85b4b9a Global Default Address Pool support
This change brings global default address pool feature into
libnetwork. Idea is to reuse same code flow and functions that were
implemented for local scope default address pool.
Function InitNetworks carries most of the changes. local scope default
address pool init should always happen only once. But Global scope
default address pool can be initialized multiple times.

Signed-off-by: selansen <elango.siva@docker.com>
2018-08-16 11:28:24 -04:00

1561 lines
40 KiB
Go

package ipam
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"math/rand"
"net"
"strconv"
"sync"
"testing"
"time"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/boltdb"
"github.com/docker/libnetwork/bitseq"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/ipamapi"
_ "github.com/docker/libnetwork/testutils"
"github.com/docker/libnetwork/types"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
const (
defaultPrefix = "/tmp/libnetwork/test/ipam"
)
func init() {
boltdb.Register()
}
// OptionBoltdbWithRandomDBFile function returns a random dir for local store backend
func randomLocalStore(needStore bool) (datastore.DataStore, error) {
if !needStore {
return nil, nil
}
tmp, err := ioutil.TempFile("", "libnetwork-")
if err != nil {
return nil, fmt.Errorf("Error creating temp file: %v", err)
}
if err := tmp.Close(); err != nil {
return nil, fmt.Errorf("Error closing temp file: %v", err)
}
return datastore.NewDataStore(datastore.LocalScope, &datastore.ScopeCfg{
Client: datastore.ScopeClientCfg{
Provider: "boltdb",
Address: defaultPrefix + tmp.Name(),
Config: &store.Config{
Bucket: "libnetwork",
ConnectionTimeout: 3 * time.Second,
},
},
})
}
func getAllocator(store bool) (*Allocator, error) {
ds, err := randomLocalStore(store)
if err != nil {
return nil, err
}
return NewAllocator(ds, nil)
}
func TestInt2IP2IntConversion(t *testing.T) {
for i := uint64(0); i < 256*256*256; i++ {
var array [4]byte // new array at each cycle
addIntToIP(array[:], i)
j := ipToUint64(array[:])
if j != i {
t.Fatalf("Failed to convert ordinal %d to IP % x and back to ordinal. Got %d", i, array, j)
}
}
}
func TestGetAddressVersion(t *testing.T) {
if v4 != getAddressVersion(net.ParseIP("172.28.30.112")) {
t.Fatal("Failed to detect IPv4 version")
}
if v4 != getAddressVersion(net.ParseIP("0.0.0.1")) {
t.Fatal("Failed to detect IPv4 version")
}
if v6 != getAddressVersion(net.ParseIP("ff01::1")) {
t.Fatal("Failed to detect IPv6 version")
}
if v6 != getAddressVersion(net.ParseIP("2001:db8::76:51")) {
t.Fatal("Failed to detect IPv6 version")
}
}
func TestKeyString(t *testing.T) {
k := &SubnetKey{AddressSpace: "default", Subnet: "172.27.0.0/16"}
expected := "default/172.27.0.0/16"
if expected != k.String() {
t.Fatalf("Unexpected key string: %s", k.String())
}
k2 := &SubnetKey{}
err := k2.FromString(expected)
if err != nil {
t.Fatal(err)
}
if k2.AddressSpace != k.AddressSpace || k2.Subnet != k.Subnet {
t.Fatalf("SubnetKey.FromString() failed. Expected %v. Got %v", k, k2)
}
expected = fmt.Sprintf("%s/%s", expected, "172.27.3.0/24")
k.ChildSubnet = "172.27.3.0/24"
if expected != k.String() {
t.Fatalf("Unexpected key string: %s", k.String())
}
err = k2.FromString(expected)
if err != nil {
t.Fatal(err)
}
if k2.AddressSpace != k.AddressSpace || k2.Subnet != k.Subnet || k2.ChildSubnet != k.ChildSubnet {
t.Fatalf("SubnetKey.FromString() failed. Expected %v. Got %v", k, k2)
}
}
func TestPoolDataMarshal(t *testing.T) {
_, nw, err := net.ParseCIDR("172.28.30.1/24")
if err != nil {
t.Fatal(err)
}
p := &PoolData{
ParentKey: SubnetKey{AddressSpace: "Blue", Subnet: "172.28.0.0/16"},
Pool: nw,
Range: &AddressRange{Sub: &net.IPNet{IP: net.IP{172, 28, 20, 0}, Mask: net.IPMask{255, 255, 255, 0}}, Start: 0, End: 255},
RefCount: 4,
}
ba, err := json.Marshal(p)
if err != nil {
t.Fatal(err)
}
var q PoolData
err = json.Unmarshal(ba, &q)
if err != nil {
t.Fatal(err)
}
if p.ParentKey != q.ParentKey || !types.CompareIPNet(p.Range.Sub, q.Range.Sub) ||
p.Range.Start != q.Range.Start || p.Range.End != q.Range.End || p.RefCount != q.RefCount ||
!types.CompareIPNet(p.Pool, q.Pool) {
t.Fatalf("\n%#v\n%#v", p, &q)
}
p = &PoolData{
ParentKey: SubnetKey{AddressSpace: "Blue", Subnet: "172.28.0.0/16"},
Pool: nw,
RefCount: 4,
}
ba, err = json.Marshal(p)
if err != nil {
t.Fatal(err)
}
err = json.Unmarshal(ba, &q)
if err != nil {
t.Fatal(err)
}
if q.Range != nil {
t.Fatal("Unexpected Range")
}
}
func TestSubnetsMarshal(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
if err != nil {
t.Fatal(err)
}
pid0, _, _, err := a.RequestPool(localAddressSpace, "192.168.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
pid1, _, _, err := a.RequestPool(localAddressSpace, "192.169.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
_, _, err = a.RequestAddress(pid0, nil, nil)
if err != nil {
t.Fatal(err)
}
cfg, err := a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
ba := cfg.Value()
if err := cfg.SetValue(ba); err != nil {
t.Fatal(err)
}
expIP := &net.IPNet{IP: net.IP{192, 168, 0, 2}, Mask: net.IPMask{255, 255, 0, 0}}
ip, _, err := a.RequestAddress(pid0, nil, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(expIP, ip) {
t.Fatalf("Got unexpected ip after pool config restore: %s", ip)
}
expIP = &net.IPNet{IP: net.IP{192, 169, 0, 1}, Mask: net.IPMask{255, 255, 0, 0}}
ip, _, err = a.RequestAddress(pid1, nil, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(expIP, ip) {
t.Fatalf("Got unexpected ip after pool config restore: %s", ip)
}
}
}
func TestAddSubnets(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
if err != nil {
t.Fatal(err)
}
a.addrSpaces["abc"] = a.addrSpaces[localAddressSpace]
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatal("Unexpected failure in adding subnet")
}
pid1, _, _, err := a.RequestPool("abc", "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatalf("Unexpected failure in adding overlapping subnets to different address spaces: %v", err)
}
if pid0 == pid1 {
t.Fatal("returned same pool id for same subnets in different namespaces")
}
_, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "", nil, false)
if err == nil {
t.Fatalf("Expected failure requesting existing subnet")
}
_, _, _, err = a.RequestPool("abc", "10.128.0.0/9", "", nil, false)
if err == nil {
t.Fatal("Expected failure on adding overlapping base subnet")
}
_, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "10.128.0.0/9", nil, false)
if err != nil {
t.Fatalf("Unexpected failure on adding sub pool: %v", err)
}
_, _, _, err = a.RequestPool("abc", "10.0.0.0/8", "10.128.0.0/9", nil, false)
if err == nil {
t.Fatalf("Expected failure on adding overlapping sub pool")
}
_, _, _, err = a.RequestPool(localAddressSpace, "10.20.2.0/24", "", nil, false)
if err == nil {
t.Fatal("Failed to detect overlapping subnets")
}
_, _, _, err = a.RequestPool(localAddressSpace, "10.128.0.0/9", "", nil, false)
if err == nil {
t.Fatal("Failed to detect overlapping subnets")
}
_, _, _, err = a.RequestPool(localAddressSpace, "1003:1:2:3:4:5:6::/112", "", nil, false)
if err != nil {
t.Fatalf("Failed to add v6 subnet: %s", err.Error())
}
_, _, _, err = a.RequestPool(localAddressSpace, "1003:1:2:3::/64", "", nil, false)
if err == nil {
t.Fatal("Failed to detect overlapping v6 subnet")
}
}
}
// TestDoublePoolRelease tests that releasing a pool which has already
// been released raises an error.
func TestDoublePoolRelease(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
assert.NilError(t, err)
err = a.ReleasePool(pid0)
assert.NilError(t, err)
err = a.ReleasePool(pid0)
assert.Check(t, is.ErrorContains(err, ""))
}
}
func TestAddReleasePoolID(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
var k0, k1 SubnetKey
aSpace, err := a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatal("Unexpected failure in adding pool")
}
if err := k0.FromString(pid0); err != nil {
t.Fatal(err)
}
aSpace, err = a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
subnets := aSpace.subnets
if subnets[k0].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
}
pid1, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
if err != nil {
t.Fatal("Unexpected failure in adding sub pool")
}
if err := k1.FromString(pid1); err != nil {
t.Fatal(err)
}
if pid0 == pid1 {
t.Fatalf("Incorrect poolIDs returned %s, %s", pid0, pid1)
}
aSpace, err = a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
subnets = aSpace.subnets
if subnets[k1].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k1, subnets[k1].RefCount)
}
_, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
if err == nil {
t.Fatal("Expected failure in adding sub pool")
}
aSpace, err = a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
subnets = aSpace.subnets
if subnets[k0].RefCount != 2 {
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
}
if err := a.ReleasePool(pid1); err != nil {
t.Fatal(err)
}
aSpace, err = a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
subnets = aSpace.subnets
if subnets[k0].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
}
if err := a.ReleasePool(pid0); err != nil {
t.Fatal(err)
}
pid00, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatal("Unexpected failure in adding pool")
}
if pid00 != pid0 {
t.Fatal("main pool should still exist")
}
aSpace, err = a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
subnets = aSpace.subnets
if subnets[k0].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
}
if err := a.ReleasePool(pid00); err != nil {
t.Fatal(err)
}
aSpace, err = a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
subnets = aSpace.subnets
if bp, ok := subnets[k0]; ok {
t.Fatalf("Base pool %s is still present: %v", k0, bp)
}
_, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatal("Unexpected failure in adding pool")
}
aSpace, err = a.getAddrSpace(localAddressSpace)
if err != nil {
t.Fatal(err)
}
subnets = aSpace.subnets
if subnets[k0].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
}
}
}
func TestPredefinedPool(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
if _, err := a.getPredefinedPool("blue", false); err == nil {
t.Fatal("Expected failure for non default addr space")
}
pid, nw, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
if err != nil {
t.Fatal(err)
}
nw2, err := a.getPredefinedPool(localAddressSpace, false)
if err != nil {
t.Fatal(err)
}
if types.CompareIPNet(nw, nw2) {
t.Fatalf("Unexpected default network returned: %s = %s", nw2, nw)
}
if err := a.ReleasePool(pid); err != nil {
t.Fatal(err)
}
}
}
func TestRemoveSubnet(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
a.addrSpaces["splane"] = &addrSpace{
id: dsConfigKey + "/" + "splane",
ds: a.addrSpaces[localAddressSpace].ds,
alloc: a.addrSpaces[localAddressSpace].alloc,
scope: a.addrSpaces[localAddressSpace].scope,
subnets: map[SubnetKey]*PoolData{},
}
input := []struct {
addrSpace string
subnet string
v6 bool
}{
{localAddressSpace, "192.168.0.0/16", false},
{localAddressSpace, "172.17.0.0/16", false},
{localAddressSpace, "10.0.0.0/8", false},
{localAddressSpace, "2001:db8:1:2:3:4:ffff::/112", false},
{"splane", "172.17.0.0/16", false},
{"splane", "10.0.0.0/8", false},
{"splane", "2001:db8:1:2:3:4:5::/112", true},
{"splane", "2001:db8:1:2:3:4:ffff::/112", true},
}
poolIDs := make([]string, len(input))
for ind, i := range input {
if poolIDs[ind], _, _, err = a.RequestPool(i.addrSpace, i.subnet, "", nil, i.v6); err != nil {
t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error())
}
}
for ind, id := range poolIDs {
if err := a.ReleasePool(id); err != nil {
t.Fatalf("Failed to release poolID %s (%d)", id, ind)
}
}
}
}
func TestGetSameAddress(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
a.addrSpaces["giallo"] = &addrSpace{
id: dsConfigKey + "/" + "giallo",
ds: a.addrSpaces[localAddressSpace].ds,
alloc: a.addrSpaces[localAddressSpace].alloc,
scope: a.addrSpaces[localAddressSpace].scope,
subnets: map[SubnetKey]*PoolData{},
}
pid, _, _, err := a.RequestPool("giallo", "192.168.100.0/24", "", nil, false)
if err != nil {
t.Fatal(err)
}
ip := net.ParseIP("192.168.100.250")
_, _, err = a.RequestAddress(pid, ip, nil)
if err != nil {
t.Fatal(err)
}
_, _, err = a.RequestAddress(pid, ip, nil)
if err == nil {
t.Fatal(err)
}
}
}
func TestPoolAllocationReuse(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
// First get all pools until they are exhausted to
pList := []string{}
pool, _, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
for err == nil {
pList = append(pList, pool)
pool, _, _, err = a.RequestPool(localAddressSpace, "", "", nil, false)
}
nPools := len(pList)
for _, pool := range pList {
if err := a.ReleasePool(pool); err != nil {
t.Fatal(err)
}
}
// Now try to allocate then free nPool pools sequentially.
// Verify that we don't see any repeat networks even though
// we have freed them.
seen := map[string]bool{}
for i := 0; i < nPools; i++ {
pool, nw, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
if err != nil {
t.Fatal(err)
}
if _, ok := seen[nw.String()]; ok {
t.Fatalf("Network %s was reused before exhausing the pool list", nw.String())
}
seen[nw.String()] = true
if err := a.ReleasePool(pool); err != nil {
t.Fatal(err)
}
}
}
}
func TestGetAddressSubPoolEqualPool(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
// Requesting a subpool of same size of the master pool should not cause any problem on ip allocation
pid, _, _, err := a.RequestPool(localAddressSpace, "172.18.0.0/16", "172.18.0.0/16", nil, false)
if err != nil {
t.Fatal(err)
}
_, _, err = a.RequestAddress(pid, nil, nil)
if err != nil {
t.Fatal(err)
}
}
}
func TestRequestReleaseAddressFromSubPool(t *testing.T) {
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
a.addrSpaces["rosso"] = &addrSpace{
id: dsConfigKey + "/" + "rosso",
ds: a.addrSpaces[localAddressSpace].ds,
alloc: a.addrSpaces[localAddressSpace].alloc,
scope: a.addrSpaces[localAddressSpace].scope,
subnets: map[SubnetKey]*PoolData{},
}
poolID, _, _, err := a.RequestPool("rosso", "172.28.0.0/16", "172.28.30.0/24", nil, false)
if err != nil {
t.Fatal(err)
}
var ip *net.IPNet
expected := &net.IPNet{IP: net.IP{172, 28, 30, 255}, Mask: net.IPMask{255, 255, 0, 0}}
for err == nil {
var c *net.IPNet
if c, _, err = a.RequestAddress(poolID, nil, nil); err == nil {
ip = c
}
}
if err != ipamapi.ErrNoAvailableIPs {
t.Fatal(err)
}
if !types.CompareIPNet(expected, ip) {
t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
}
rp := &net.IPNet{IP: net.IP{172, 28, 30, 97}, Mask: net.IPMask{255, 255, 0, 0}}
if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(rp, ip) {
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
}
_, _, _, err = a.RequestPool("rosso", "10.0.0.0/8", "10.0.0.0/16", nil, false)
if err != nil {
t.Fatal(err)
}
poolID, _, _, err = a.RequestPool("rosso", "10.0.0.0/16", "10.0.0.0/24", nil, false)
if err != nil {
t.Fatal(err)
}
expected = &net.IPNet{IP: net.IP{10, 0, 0, 255}, Mask: net.IPMask{255, 255, 0, 0}}
for err == nil {
var c *net.IPNet
if c, _, err = a.RequestAddress(poolID, nil, nil); err == nil {
ip = c
}
}
if err != ipamapi.ErrNoAvailableIPs {
t.Fatal(err)
}
if !types.CompareIPNet(expected, ip) {
t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
}
rp = &net.IPNet{IP: net.IP{10, 0, 0, 79}, Mask: net.IPMask{255, 255, 0, 0}}
if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(rp, ip) {
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
}
// Request any addresses from subpool after explicit address request
unoExp, _ := types.ParseCIDR("10.2.2.0/16")
dueExp, _ := types.ParseCIDR("10.2.2.2/16")
treExp, _ := types.ParseCIDR("10.2.2.1/16")
if poolID, _, _, err = a.RequestPool("rosso", "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
t.Fatal(err)
}
tre, _, err := a.RequestAddress(poolID, treExp.IP, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(tre, treExp) {
t.Fatalf("Unexpected address: %v", tre)
}
uno, _, err := a.RequestAddress(poolID, nil, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(uno, unoExp) {
t.Fatalf("Unexpected address: %v", uno)
}
due, _, err := a.RequestAddress(poolID, nil, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(due, dueExp) {
t.Fatalf("Unexpected address: %v", due)
}
if err = a.ReleaseAddress(poolID, uno.IP); err != nil {
t.Fatal(err)
}
uno, _, err = a.RequestAddress(poolID, nil, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(uno, unoExp) {
t.Fatalf("Unexpected address: %v", uno)
}
if err = a.ReleaseAddress(poolID, tre.IP); err != nil {
t.Fatal(err)
}
tre, _, err = a.RequestAddress(poolID, nil, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(tre, treExp) {
t.Fatalf("Unexpected address: %v", tre)
}
}
}
func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
opts := map[string]string{
ipamapi.AllocSerialPrefix: "true"}
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
a.addrSpaces["rosso"] = &addrSpace{
id: dsConfigKey + "/" + "rosso",
ds: a.addrSpaces[localAddressSpace].ds,
alloc: a.addrSpaces[localAddressSpace].alloc,
scope: a.addrSpaces[localAddressSpace].scope,
subnets: map[SubnetKey]*PoolData{},
}
poolID, _, _, err := a.RequestPool("rosso", "172.28.0.0/16", "172.28.30.0/24", nil, false)
if err != nil {
t.Fatal(err)
}
var ip *net.IPNet
expected := &net.IPNet{IP: net.IP{172, 28, 30, 255}, Mask: net.IPMask{255, 255, 0, 0}}
for err == nil {
var c *net.IPNet
if c, _, err = a.RequestAddress(poolID, nil, opts); err == nil {
ip = c
}
}
if err != ipamapi.ErrNoAvailableIPs {
t.Fatal(err)
}
if !types.CompareIPNet(expected, ip) {
t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
}
rp := &net.IPNet{IP: net.IP{172, 28, 30, 97}, Mask: net.IPMask{255, 255, 0, 0}}
if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, opts); err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(rp, ip) {
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
}
_, _, _, err = a.RequestPool("rosso", "10.0.0.0/8", "10.0.0.0/16", nil, false)
if err != nil {
t.Fatal(err)
}
poolID, _, _, err = a.RequestPool("rosso", "10.0.0.0/16", "10.0.0.0/24", nil, false)
if err != nil {
t.Fatal(err)
}
expected = &net.IPNet{IP: net.IP{10, 0, 0, 255}, Mask: net.IPMask{255, 255, 0, 0}}
for err == nil {
var c *net.IPNet
if c, _, err = a.RequestAddress(poolID, nil, opts); err == nil {
ip = c
}
}
if err != ipamapi.ErrNoAvailableIPs {
t.Fatal(err)
}
if !types.CompareIPNet(expected, ip) {
t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
}
rp = &net.IPNet{IP: net.IP{10, 0, 0, 79}, Mask: net.IPMask{255, 255, 0, 0}}
if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, opts); err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(rp, ip) {
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
}
// Request any addresses from subpool after explicit address request
unoExp, _ := types.ParseCIDR("10.2.2.0/16")
dueExp, _ := types.ParseCIDR("10.2.2.2/16")
treExp, _ := types.ParseCIDR("10.2.2.1/16")
quaExp, _ := types.ParseCIDR("10.2.2.3/16")
fivExp, _ := types.ParseCIDR("10.2.2.4/16")
if poolID, _, _, err = a.RequestPool("rosso", "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
t.Fatal(err)
}
tre, _, err := a.RequestAddress(poolID, treExp.IP, opts)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(tre, treExp) {
t.Fatalf("Unexpected address: %v", tre)
}
uno, _, err := a.RequestAddress(poolID, nil, opts)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(uno, unoExp) {
t.Fatalf("Unexpected address: %v", uno)
}
due, _, err := a.RequestAddress(poolID, nil, opts)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(due, dueExp) {
t.Fatalf("Unexpected address: %v", due)
}
if err = a.ReleaseAddress(poolID, uno.IP); err != nil {
t.Fatal(err)
}
uno, _, err = a.RequestAddress(poolID, nil, opts)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(uno, quaExp) {
t.Fatalf("Unexpected address: %v", uno)
}
if err = a.ReleaseAddress(poolID, tre.IP); err != nil {
t.Fatal(err)
}
tre, _, err = a.RequestAddress(poolID, nil, opts)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(tre, fivExp) {
t.Fatalf("Unexpected address: %v", tre)
}
}
}
func TestGetAddress(t *testing.T) {
input := []string{
/*"10.0.0.0/8", "10.0.0.0/9", "10.0.0.0/10",*/ "10.0.0.0/11", "10.0.0.0/12", "10.0.0.0/13", "10.0.0.0/14",
"10.0.0.0/15", "10.0.0.0/16", "10.0.0.0/17", "10.0.0.0/18", "10.0.0.0/19", "10.0.0.0/20", "10.0.0.0/21",
"10.0.0.0/22", "10.0.0.0/23", "10.0.0.0/24", "10.0.0.0/25", "10.0.0.0/26", "10.0.0.0/27", "10.0.0.0/28",
"10.0.0.0/29", "10.0.0.0/30", "10.0.0.0/31"}
for _, subnet := range input {
assertGetAddress(t, subnet)
}
}
func TestRequestSyntaxCheck(t *testing.T) {
var (
pool = "192.168.0.0/16"
subPool = "192.168.0.0/24"
as = "green"
)
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
a.addrSpaces[as] = &addrSpace{
id: dsConfigKey + "/" + as,
ds: a.addrSpaces[localAddressSpace].ds,
alloc: a.addrSpaces[localAddressSpace].alloc,
scope: a.addrSpaces[localAddressSpace].scope,
subnets: map[SubnetKey]*PoolData{},
}
_, _, _, err = a.RequestPool("", pool, "", nil, false)
if err == nil {
t.Fatal("Failed to detect wrong request: empty address space")
}
_, _, _, err = a.RequestPool("", pool, subPool, nil, false)
if err == nil {
t.Fatal("Failed to detect wrong request: empty address space")
}
_, _, _, err = a.RequestPool(as, "", subPool, nil, false)
if err == nil {
t.Fatal("Failed to detect wrong request: subPool specified and no pool")
}
pid, _, _, err := a.RequestPool(as, pool, subPool, nil, false)
if err != nil {
t.Fatalf("Unexpected failure: %v", err)
}
_, _, err = a.RequestAddress("", nil, nil)
if err == nil {
t.Fatal("Failed to detect wrong request: no pool id specified")
}
ip := net.ParseIP("172.17.0.23")
_, _, err = a.RequestAddress(pid, ip, nil)
if err == nil {
t.Fatal("Failed to detect wrong request: requested IP from different subnet")
}
ip = net.ParseIP("192.168.0.50")
_, _, err = a.RequestAddress(pid, ip, nil)
if err != nil {
t.Fatalf("Unexpected failure: %v", err)
}
err = a.ReleaseAddress("", ip)
if err == nil {
t.Fatal("Failed to detect wrong request: no pool id specified")
}
err = a.ReleaseAddress(pid, nil)
if err == nil {
t.Fatal("Failed to detect wrong request: no pool id specified")
}
err = a.ReleaseAddress(pid, ip)
if err != nil {
t.Fatalf("Unexpected failure: %v: %s, %s", err, pid, ip)
}
}
}
func TestRequest(t *testing.T) {
// Request N addresses from different size subnets, verifying last request
// returns expected address. Internal subnet host size is Allocator's default, 16
input := []struct {
subnet string
numReq int
lastIP string
}{
{"192.168.59.0/24", 254, "192.168.59.254"},
{"192.168.240.0/20", 255, "192.168.240.255"},
{"192.168.0.0/16", 255, "192.168.0.255"},
{"192.168.0.0/16", 256, "192.168.1.0"},
{"10.16.0.0/16", 255, "10.16.0.255"},
{"10.128.0.0/12", 255, "10.128.0.255"},
{"10.0.0.0/8", 256, "10.0.1.0"},
{"192.168.128.0/18", 4*256 - 1, "192.168.131.255"},
/*
{"192.168.240.0/20", 16*256 - 2, "192.168.255.254"},
{"192.168.0.0/16", 256*256 - 2, "192.168.255.254"},
{"10.0.0.0/8", 2 * 256, "10.0.2.0"},
{"10.0.0.0/8", 5 * 256, "10.0.5.0"},
{"10.0.0.0/8", 100 * 256 * 254, "10.99.255.254"},
*/
}
for _, d := range input {
assertNRequests(t, d.subnet, d.numReq, d.lastIP)
}
}
// TestOverlappingRequests tests that overlapping subnets cannot be allocated.
// Requests for subnets which are supersets or subsets of existing allocations,
// or which overlap at the beginning or end, should not be permitted.
func TestOverlappingRequests(t *testing.T) {
input := []struct {
environment []string
subnet string
ok bool
}{
// IPv4
// Previously allocated network does not overlap with request
{[]string{"10.0.0.0/8"}, "11.0.0.0/8", true},
{[]string{"74.0.0.0/7"}, "9.111.99.72/30", true},
{[]string{"110.192.0.0/10"}, "16.0.0.0/10", true},
// Previously allocated network entirely contains request
{[]string{"10.0.0.0/8"}, "10.0.0.0/8", false}, // exact overlap
{[]string{"0.0.0.0/1"}, "16.182.0.0/15", false},
{[]string{"16.0.0.0/4"}, "17.11.66.0/23", false},
// Previously allocated network overlaps beginning of request
{[]string{"0.0.0.0/1"}, "0.0.0.0/0", false},
{[]string{"64.0.0.0/6"}, "64.0.0.0/3", false},
{[]string{"112.0.0.0/6"}, "112.0.0.0/4", false},
// Previously allocated network overlaps end of request
{[]string{"96.0.0.0/3"}, "0.0.0.0/1", false},
{[]string{"192.0.0.0/2"}, "128.0.0.0/1", false},
{[]string{"95.0.0.0/8"}, "92.0.0.0/6", false},
// Previously allocated network entirely contained within request
{[]string{"10.0.0.0/8"}, "10.0.0.0/6", false}, // non-canonical
{[]string{"10.0.0.0/8"}, "8.0.0.0/6", false}, // canonical
{[]string{"25.173.144.0/20"}, "0.0.0.0/0", false},
// IPv6
// Previously allocated network entirely contains request
{[]string{"::/0"}, "f656:3484:c878:a05:e540:a6ed:4d70:3740/123", false},
{[]string{"8000::/1"}, "8fe8:e7c4:5779::/49", false},
{[]string{"f000::/4"}, "ffc7:6000::/19", false},
// Previously allocated network overlaps beginning of request
{[]string{"::/2"}, "::/0", false},
{[]string{"::/3"}, "::/1", false},
{[]string{"::/6"}, "::/5", false},
// Previously allocated network overlaps end of request
{[]string{"c000::/2"}, "8000::/1", false},
{[]string{"7c00::/6"}, "::/1", false},
{[]string{"cf80::/9"}, "c000::/4", false},
// Previously allocated network entirely contained within request
{[]string{"ff77:93f8::/29"}, "::/0", false},
{[]string{"9287:2e20:5134:fab6:9061:a0c6:bfe3:9400/119"}, "8000::/1", false},
{[]string{"3ea1:bfa9:8691:d1c6:8c46:519b:db6d:e700/120"}, "3000::/4", false},
}
for _, store := range []bool{false, true} {
for _, tc := range input {
a, err := getAllocator(store)
assert.NilError(t, err)
// Set up some existing allocations. This should always succeed.
for _, env := range tc.environment {
_, _, _, err = a.RequestPool(localAddressSpace, env, "", nil, false)
assert.NilError(t, err)
}
// Make the test allocation.
_, _, _, err = a.RequestPool(localAddressSpace, tc.subnet, "", nil, false)
if tc.ok {
assert.NilError(t, err)
} else {
assert.Check(t, is.ErrorContains(err, ""))
}
}
}
}
func TestRelease(t *testing.T) {
var (
subnet = "192.168.0.0/23"
)
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
if err != nil {
t.Fatal(err)
}
bm := a.addresses[SubnetKey{localAddressSpace, subnet, ""}]
// Allocate all addresses
for err != ipamapi.ErrNoAvailableIPs {
_, _, err = a.RequestAddress(pid, nil, nil)
}
toRelease := []struct {
address string
}{
{"192.168.0.1"},
{"192.168.0.2"},
{"192.168.0.3"},
{"192.168.0.4"},
{"192.168.0.5"},
{"192.168.0.6"},
{"192.168.0.7"},
{"192.168.0.8"},
{"192.168.0.9"},
{"192.168.0.10"},
{"192.168.0.30"},
{"192.168.0.31"},
{"192.168.1.32"},
{"192.168.0.254"},
{"192.168.1.1"},
{"192.168.1.2"},
{"192.168.1.3"},
{"192.168.1.253"},
{"192.168.1.254"},
}
// One by one, release the address and request again. We should get the same IP
for i, inp := range toRelease {
ip0 := net.ParseIP(inp.address)
a.ReleaseAddress(pid, ip0)
bm = a.addresses[SubnetKey{localAddressSpace, subnet, ""}]
if bm.Unselected() != 1 {
t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected())
}
nw, _, err := a.RequestAddress(pid, nil, nil)
if err != nil {
t.Fatalf("Failed to obtain the address: %s", err.Error())
}
ip := nw.IP
if !ip0.Equal(ip) {
t.Fatalf("Failed to obtain the same address. Expected: %s, Got: %s", ip0, ip)
}
}
}
}
func assertGetAddress(t *testing.T, subnet string) {
var (
err error
printTime = false
a = &Allocator{}
)
_, sub, _ := net.ParseCIDR(subnet)
ones, bits := sub.Mask.Size()
zeroes := bits - ones
numAddresses := 1 << uint(zeroes)
bm, err := bitseq.NewHandle("ipam_test", nil, "default/"+subnet, uint64(numAddresses))
if err != nil {
t.Fatal(err)
}
start := time.Now()
run := 0
for err != ipamapi.ErrNoAvailableIPs {
_, err = a.getAddress(sub, bm, nil, nil, false)
run++
}
if printTime {
fmt.Printf("\nTaken %v, to allocate all addresses on %s. (nemAddresses: %d. Runs: %d)", time.Since(start), subnet, numAddresses, run)
}
if bm.Unselected() != 0 {
t.Fatalf("Unexpected free count after reserving all addresses: %d", bm.Unselected())
}
/*
if bm.Head.Block != expectedMax || bm.Head.Count != numBlocks {
t.Fatalf("Failed to effectively reserve all addresses on %s. Expected (0x%x, %d) as first sequence. Found (0x%x,%d)",
subnet, expectedMax, numBlocks, bm.Head.Block, bm.Head.Count)
}
*/
}
func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP string) {
var (
nw *net.IPNet
printTime = false
)
lastIP := net.ParseIP(lastExpectedIP)
for _, store := range []bool{false, true} {
a, err := getAllocator(store)
assert.NilError(t, err)
pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
if err != nil {
t.Fatal(err)
}
i := 0
start := time.Now()
for ; i < numReq; i++ {
nw, _, err = a.RequestAddress(pid, nil, nil)
}
if printTime {
fmt.Printf("\nTaken %v, to allocate %d addresses on %s\n", time.Since(start), numReq, subnet)
}
if !lastIP.Equal(nw.IP) {
t.Fatalf("Wrong last IP. Expected %s. Got: %s (err: %v, ind: %d)", lastExpectedIP, nw.IP.String(), err, i)
}
}
}
func benchmarkRequest(b *testing.B, a *Allocator, subnet string) {
pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
for err != ipamapi.ErrNoAvailableIPs {
_, _, err = a.RequestAddress(pid, nil, nil)
}
}
func benchMarkRequest(subnet string, b *testing.B) {
a, _ := getAllocator(true)
for n := 0; n < b.N; n++ {
benchmarkRequest(b, a, subnet)
}
}
func BenchmarkRequest(b *testing.B) {
subnets := []string{
"10.0.0.0/24",
"10.0.0.0/16",
"10.0.0.0/8",
}
for _, subnet := range subnets {
name := fmt.Sprintf("%vSubnet", subnet)
b.Run(name, func(b *testing.B) {
a, _ := getAllocator(true)
benchmarkRequest(b, a, subnet)
})
}
}
func TestAllocateRandomDeallocate(t *testing.T) {
for _, store := range []bool{false, true} {
testAllocateRandomDeallocate(t, "172.25.0.0/16", "", 384, store)
testAllocateRandomDeallocate(t, "172.25.0.0/16", "172.25.252.0/22", 384, store)
}
}
func testAllocateRandomDeallocate(t *testing.T, pool, subPool string, num int, store bool) {
ds, err := randomLocalStore(store)
assert.NilError(t, err)
a, err := NewAllocator(ds, nil)
if err != nil {
t.Fatal(err)
}
pid, _, _, err := a.RequestPool(localAddressSpace, pool, subPool, nil, false)
if err != nil {
t.Fatal(err)
}
// Allocate num ip addresses
indices := make(map[int]*net.IPNet, num)
allocated := make(map[string]bool, num)
for i := 0; i < num; i++ {
ip, _, err := a.RequestAddress(pid, nil, nil)
if err != nil {
t.Fatal(err)
}
ips := ip.String()
if _, ok := allocated[ips]; ok {
t.Fatalf("Address %s is already allocated", ips)
}
allocated[ips] = true
indices[i] = ip
}
if len(indices) != len(allocated) || len(indices) != num {
t.Fatalf("Unexpected number of allocated addresses: (%d,%d).", len(indices), len(allocated))
}
seed := time.Now().Unix()
rand.Seed(seed)
// Deallocate half of the allocated addresses following a random pattern
pattern := rand.Perm(num)
for i := 0; i < num/2; i++ {
idx := pattern[i]
ip := indices[idx]
err := a.ReleaseAddress(pid, ip.IP)
if err != nil {
t.Fatalf("Unexpected failure on deallocation of %s: %v.\nSeed: %d.", ip, err, seed)
}
delete(indices, idx)
delete(allocated, ip.String())
}
// Request a quarter of addresses
for i := 0; i < num/2; i++ {
ip, _, err := a.RequestAddress(pid, nil, nil)
if err != nil {
t.Fatal(err)
}
ips := ip.String()
if _, ok := allocated[ips]; ok {
t.Fatalf("\nAddress %s is already allocated.\nSeed: %d.", ips, seed)
}
allocated[ips] = true
}
if len(allocated) != num {
t.Fatalf("Unexpected number of allocated addresses: %d.\nSeed: %d.", len(allocated), seed)
}
}
func TestRetrieveFromStore(t *testing.T) {
num := 200
ds, err := randomLocalStore(true)
if err != nil {
t.Fatal(err)
}
a, err := NewAllocator(ds, nil)
if err != nil {
t.Fatal(err)
}
pid, _, _, err := a.RequestPool(localAddressSpace, "172.25.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
for i := 0; i < num; i++ {
if _, _, err := a.RequestAddress(pid, nil, nil); err != nil {
t.Fatal(err)
}
}
// Restore
a1, err := NewAllocator(ds, nil)
if err != nil {
t.Fatal(err)
}
a1.refresh(localAddressSpace)
db := a.DumpDatabase()
db1 := a1.DumpDatabase()
if db != db1 {
t.Fatalf("Unexpected db change.\nExpected:%s\nGot:%s", db, db1)
}
checkDBEquality(a, a1, t)
pid, _, _, err = a1.RequestPool(localAddressSpace, "172.25.0.0/16", "172.25.1.0/24", nil, false)
if err != nil {
t.Fatal(err)
}
for i := 0; i < num/2; i++ {
if _, _, err := a1.RequestAddress(pid, nil, nil); err != nil {
t.Fatal(err)
}
}
// Restore
a2, err := NewAllocator(ds, nil)
if err != nil {
t.Fatal(err)
}
a2.refresh(localAddressSpace)
checkDBEquality(a1, a2, t)
pid, _, _, err = a2.RequestPool(localAddressSpace, "172.25.0.0/16", "172.25.2.0/24", nil, false)
if err != nil {
t.Fatal(err)
}
for i := 0; i < num/2; i++ {
if _, _, err := a2.RequestAddress(pid, nil, nil); err != nil {
t.Fatal(err)
}
}
// Restore
a3, err := NewAllocator(ds, nil)
if err != nil {
t.Fatal(err)
}
a3.refresh(localAddressSpace)
checkDBEquality(a2, a3, t)
pid, _, _, err = a3.RequestPool(localAddressSpace, "172.26.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
for i := 0; i < num/2; i++ {
if _, _, err := a3.RequestAddress(pid, nil, nil); err != nil {
t.Fatal(err)
}
}
// Restore
a4, err := NewAllocator(ds, nil)
if err != nil {
t.Fatal(err)
}
a4.refresh(localAddressSpace)
checkDBEquality(a3, a4, t)
}
func checkDBEquality(a1, a2 *Allocator, t *testing.T) {
for k, cnf1 := range a1.addrSpaces[localAddressSpace].subnets {
cnf2 := a2.addrSpaces[localAddressSpace].subnets[k]
if cnf1.String() != cnf2.String() {
t.Fatalf("%s\n%s", cnf1, cnf2)
}
if cnf1.Range == nil {
a2.retrieveBitmask(k, cnf1.Pool)
}
}
for k, bm1 := range a1.addresses {
bm2 := a2.addresses[k]
if bm1.String() != bm2.String() {
t.Fatalf("%s\n%s", bm1, bm2)
}
}
}
const (
numInstances = 5
first = 0
last = numInstances - 1
)
var (
allocator *Allocator
start = make(chan struct{})
done = make(chan chan struct{}, numInstances-1)
pools = make([]*net.IPNet, numInstances)
)
func runParallelTests(t *testing.T, instance int) {
var err error
t.Parallel()
pTest := flag.Lookup("test.parallel")
if pTest == nil {
t.Skip("Skipped because test.parallel flag not set;")
}
numParallel, err := strconv.Atoi(pTest.Value.String())
if err != nil {
t.Fatal(err)
}
if numParallel < numInstances {
t.Skip("Skipped because t.parallel was less than ", numInstances)
}
// The first instance creates the allocator, gives the start
// and finally checks the pools each instance was assigned
if instance == first {
allocator, err = getAllocator(true)
if err != nil {
t.Fatal(err)
}
close(start)
}
if instance != first {
select {
case <-start:
}
instDone := make(chan struct{})
done <- instDone
defer close(instDone)
if instance == last {
defer close(done)
}
}
_, pools[instance], _, err = allocator.RequestPool(localAddressSpace, "", "", nil, false)
if err != nil {
t.Fatal(err)
}
if instance == first {
for instDone := range done {
select {
case <-instDone:
}
}
// Now check each instance got a different pool
for i := 0; i < numInstances; i++ {
for j := i + 1; j < numInstances; j++ {
if types.CompareIPNet(pools[i], pools[j]) {
t.Fatalf("Instance %d and %d were given the same predefined pool: %v", i, j, pools)
}
}
}
}
}
func TestRequestReleaseAddressDuplicate(t *testing.T) {
a, err := getAllocator(false)
if err != nil {
t.Fatal(err)
}
type IP struct {
ip *net.IPNet
ref int
}
ips := []IP{}
allocatedIPs := []*net.IPNet{}
a.addrSpaces["rosso"] = &addrSpace{
id: dsConfigKey + "/" + "rosso",
ds: a.addrSpaces[localAddressSpace].ds,
alloc: a.addrSpaces[localAddressSpace].alloc,
scope: a.addrSpaces[localAddressSpace].scope,
subnets: map[SubnetKey]*PoolData{},
}
var wg sync.WaitGroup
opts := map[string]string{
ipamapi.AllocSerialPrefix: "true",
}
var l sync.Mutex
poolID, _, _, err := a.RequestPool("rosso", "198.168.0.0/23", "", nil, false)
if err != nil {
t.Fatal(err)
}
for err == nil {
var c *net.IPNet
if c, _, err = a.RequestAddress(poolID, nil, opts); err == nil {
l.Lock()
ips = append(ips, IP{c, 1})
l.Unlock()
allocatedIPs = append(allocatedIPs, c)
if len(allocatedIPs) > 500 {
i := rand.Intn(len(allocatedIPs) - 1)
wg.Add(1)
go func(ip *net.IPNet) {
if err = a.ReleaseAddress(poolID, ip.IP); err != nil {
t.Fatal(err)
}
l.Lock()
ips = append(ips, IP{ip, -1})
l.Unlock()
wg.Done()
}(allocatedIPs[i])
allocatedIPs = append(allocatedIPs[:i], allocatedIPs[i+1:]...)
}
}
}
wg.Wait()
refMap := make(map[string]int)
for _, ip := range ips {
refMap[ip.ip.String()] = refMap[ip.ip.String()] + ip.ref
if refMap[ip.ip.String()] < 0 {
t.Fatalf("IP %s was previously released", ip.ip.String())
}
if refMap[ip.ip.String()] > 1 {
t.Fatalf("IP %s was previously allocated", ip.ip.String())
}
}
}
func TestParallelPredefinedRequest1(t *testing.T) {
runParallelTests(t, 0)
}
func TestParallelPredefinedRequest2(t *testing.T) {
runParallelTests(t, 1)
}
func TestParallelPredefinedRequest3(t *testing.T) {
runParallelTests(t, 2)
}
func TestParallelPredefinedRequest4(t *testing.T) {
runParallelTests(t, 3)
}
func TestParallelPredefinedRequest5(t *testing.T) {
runParallelTests(t, 4)
}