libnet/ipamapi: add in/out structs for RequestPool

The `RequestPool` method has many args and named returns. This
makes the code hard to follow at times. This commit adds one struct,
`PoolRequest`, to replace these args, and one struct, `AllocatedPool`,
to replace these named returns.

Both structs' fields are properly documented to better define their
semantics, and their relationship with address allocation.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
Albin Kerouanton 2024-04-17 11:30:41 +02:00
parent c945239e73
commit 9c4c3d7443
13 changed files with 363 additions and 336 deletions

View file

@ -884,13 +884,18 @@ func (na *cnmNetworkAllocator) allocatePools(n *api.Network) (map[string]string,
}
for i, ic := range ipamConfigs {
poolID, poolIP, meta, err := ipam.RequestPool(asName, ic.Subnet, ic.Range, dOptions, false)
alloc, err := ipam.RequestPool(ipamapi.PoolRequest{
AddressSpace: asName,
Pool: ic.Subnet,
SubPool: ic.Range,
Options: dOptions,
})
if err != nil {
// Rollback by releasing all the resources allocated so far.
releasePools(ipam, ipamConfigs[:i], pools)
return nil, err
}
pools[poolIP.String()] = poolID
pools[alloc.Pool.String()] = alloc.PoolID
// The IPAM contract allows the IPAM driver to autonomously
// provide a network gateway in response to the pool request.
@ -902,7 +907,7 @@ func (na *cnmNetworkAllocator) allocatePools(n *api.Network) (map[string]string,
gwIP *net.IPNet
ip net.IP
)
if gws, ok := meta[netlabel.Gateway]; ok {
if gws, ok := alloc.Meta[netlabel.Gateway]; ok {
if ip, gwIP, err = net.ParseCIDR(gws); err != nil {
return nil, fmt.Errorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err)
}
@ -917,7 +922,7 @@ func (na *cnmNetworkAllocator) allocatePools(n *api.Network) (map[string]string,
defer delete(dOptions, ipamapi.RequestAddressType)
if ic.Gateway != "" || gwIP == nil {
gwIP, _, err = ipam.RequestAddress(poolID, net.ParseIP(ic.Gateway), dOptions)
gwIP, _, err = ipam.RequestAddress(alloc.PoolID, net.ParseIP(ic.Gateway), dOptions)
if err != nil {
// Rollback by releasing all the resources allocated so far.
releasePools(ipam, ipamConfigs[:i], pools)
@ -926,7 +931,7 @@ func (na *cnmNetworkAllocator) allocatePools(n *api.Network) (map[string]string,
}
if ic.Subnet == "" {
ic.Subnet = poolIP.String()
ic.Subnet = alloc.Pool.String()
}
if ic.Gateway == "" {

View file

@ -3,9 +3,10 @@ package cnmallocator
import (
"fmt"
"net"
"net/netip"
"testing"
"github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/moby/swarmkit/v2/api"
"github.com/moby/swarmkit/v2/manager/allocator/networkallocator"
"gotest.tools/v3/assert"
@ -728,11 +729,14 @@ func (a *mockIpam) GetDefaultAddressSpaces() (string, string, error) {
return "defaultAS", "defaultAS", nil
}
func (a *mockIpam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
a.actualIpamOptions = options
func (a *mockIpam) RequestPool(req ipamapi.PoolRequest) (ipamapi.AllocatedPool, error) {
a.actualIpamOptions = req.Options
poolCidr, _ := types.ParseCIDR(pool)
return fmt.Sprintf("%s/%s", "defaultAS", pool), poolCidr, nil, nil
poolCidr := netip.MustParsePrefix(req.Pool)
return ipamapi.AllocatedPool{
PoolID: fmt.Sprintf("defaultAS/%s", req.Pool),
Pool: poolCidr,
}, nil
}
func (a *mockIpam) ReleasePool(poolID string) error {

View file

@ -91,41 +91,35 @@ func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) {
// If requestedPool is the empty string then the default predefined pool for addressSpace will be used, otherwise pool must be a valid IP address and length in CIDR notation.
// If requestedSubPool is not empty, it must be a valid IP address and length in CIDR notation which is a sub-range of requestedPool.
// requestedSubPool must be empty if requestedPool is empty.
func (a *Allocator) RequestPool(addressSpace, requestedPool, requestedSubPool string, _ map[string]string, v6 bool) (poolID string, pool *net.IPNet, meta map[string]string, err error) {
log.G(context.TODO()).Debugf("RequestPool(%s, %s, %s, _, %t)", addressSpace, requestedPool, requestedSubPool, v6)
func (a *Allocator) RequestPool(req ipamapi.PoolRequest) (ipamapi.AllocatedPool, error) {
log.G(context.TODO()).Debugf("RequestPool: %+v", req)
parseErr := func(err error) error {
return types.InternalErrorf("failed to parse pool request for address space %q pool %q subpool %q: %v", addressSpace, requestedPool, requestedSubPool, err)
if req.AddressSpace == "" {
return ipamapi.AllocatedPool{}, types.InternalErrorf("invalid pool request: empty address space")
}
if addressSpace == "" {
return "", nil, nil, parseErr(ipamapi.ErrInvalidAddressSpace)
}
aSpace, err := a.getAddrSpace(addressSpace, v6)
aSpace, err := a.getAddrSpace(req.AddressSpace, req.V6)
if err != nil {
return "", nil, nil, err
return ipamapi.AllocatedPool{}, types.InternalErrorf("invalid pool request: invalid address space")
}
if requestedPool == "" && requestedSubPool != "" {
return "", nil, nil, parseErr(ipamapi.ErrInvalidSubPool)
if req.Pool == "" && req.SubPool != "" {
return ipamapi.AllocatedPool{}, types.InternalErrorf("invalid pool request: %v", ipamapi.ErrInvalidSubPool)
}
k := PoolID{AddressSpace: addressSpace}
if requestedPool == "" {
k.Subnet, err = aSpace.allocatePredefinedPool(v6)
if err != nil {
return "", nil, nil, err
k := PoolID{AddressSpace: req.AddressSpace}
if req.Pool == "" {
if k.Subnet, err = aSpace.allocatePredefinedPool(req.V6); err != nil {
return ipamapi.AllocatedPool{}, err
}
return k.String(), netiputil.ToIPNet(k.Subnet), nil, nil
return ipamapi.AllocatedPool{PoolID: k.String(), Pool: k.Subnet}, nil
}
if k.Subnet, err = netip.ParsePrefix(requestedPool); err != nil {
return "", nil, nil, parseErr(ipamapi.ErrInvalidPool)
if k.Subnet, err = netip.ParsePrefix(req.Pool); err != nil {
return ipamapi.AllocatedPool{}, types.InternalErrorf("invalid pool request: %v", err)
}
if requestedSubPool != "" {
k.ChildSubnet, err = netip.ParsePrefix(requestedSubPool)
if err != nil {
return "", nil, nil, parseErr(ipamapi.ErrInvalidSubPool)
if req.SubPool != "" {
if k.ChildSubnet, err = netip.ParsePrefix(req.SubPool); err != nil {
return ipamapi.AllocatedPool{}, types.InternalErrorf("invalid pool request: %v", ipamapi.ErrInvalidSubPool)
}
}
@ -140,10 +134,10 @@ func (a *Allocator) RequestPool(addressSpace, requestedPool, requestedSubPool st
err = aSpace.allocateSubnet(k.Subnet, k.ChildSubnet)
if err != nil {
return "", nil, nil, err
return ipamapi.AllocatedPool{}, types.ForbiddenErrorf("invalid pool request: %v", err)
}
return k.String(), netiputil.ToIPNet(k.Subnet), nil, nil
return ipamapi.AllocatedPool{PoolID: k.String(), Pool: k.Subnet}, nil
}
// ReleasePool releases the address pool identified by the passed id

View file

@ -14,6 +14,7 @@ import (
"time"
"github.com/docker/docker/libnetwork/bitmap"
"github.com/docker/docker/libnetwork/internal/netiputil"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/docker/docker/libnetwork/types"
@ -58,55 +59,55 @@ func TestAddSubnets(t *testing.T) {
t.Fatal(err)
}
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
alloc1, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8"})
if err != nil {
t.Fatal("Unexpected failure in adding subnet")
}
pid1, _, _, err := a.RequestPool(globalAddressSpace, "10.0.0.0/8", "", nil, false)
alloc2, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: globalAddressSpace, Pool: "10.0.0.0/8"})
if err != nil {
t.Fatalf("Unexpected failure in adding overlapping subnets to different address spaces: %v", err)
}
if pid0 == pid1 {
if alloc1.PoolID == alloc2.PoolID {
t.Fatal("returned same pool id for same subnets in different namespaces")
}
_, _, _, err = a.RequestPool(globalAddressSpace, "10.0.0.0/8", "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: globalAddressSpace, Pool: "10.0.0.0/8"})
if err == nil {
t.Fatalf("Expected failure requesting existing subnet")
}
_, _, _, err = a.RequestPool(globalAddressSpace, "10.128.0.0/9", "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: globalAddressSpace, Pool: "10.128.0.0/9"})
if err == nil {
t.Fatal("Expected failure on adding overlapping base subnet")
}
_, _, _, err = a.RequestPool(globalAddressSpace, "10.0.0.0/8", "10.128.0.0/9", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: globalAddressSpace, Pool: "10.0.0.0/8", SubPool: "10.128.0.0/9"})
if err != nil {
t.Fatalf("Unexpected failure on adding sub pool: %v", err)
}
_, _, _, err = a.RequestPool(globalAddressSpace, "10.0.0.0/8", "10.128.0.0/9", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: globalAddressSpace, Pool: "10.0.0.0/8", SubPool: "10.128.0.0/9"})
if err == nil {
t.Fatalf("Expected failure on adding overlapping sub pool")
}
_, _, _, err = a.RequestPool(localAddressSpace, "10.20.2.0/24", "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.20.2.0/24"})
if err == nil {
t.Fatal("Failed to detect overlapping subnets")
}
_, _, _, err = a.RequestPool(localAddressSpace, "10.128.0.0/9", "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.128.0.0/9"})
if err == nil {
t.Fatal("Failed to detect overlapping subnets")
}
_, _, _, err = a.RequestPool(localAddressSpace, "1003:1:2:3:4:5:6::/112", "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "1003:1:2:3:4:5:6::/112"})
if err != nil {
t.Fatalf("Failed to add v6 subnet: %s", err.Error())
}
_, _, _, err = a.RequestPool(localAddressSpace, "1003:1:2:3::/64", "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "1003:1:2:3::/64"})
if err == nil {
t.Fatal("Failed to detect overlapping v6 subnet")
}
@ -118,13 +119,13 @@ func TestDoublePoolRelease(t *testing.T) {
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
alloc1, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8"})
assert.NilError(t, err)
err = a.ReleasePool(pid0)
err = a.ReleasePool(alloc1.PoolID)
assert.NilError(t, err)
err = a.ReleasePool(pid0)
err = a.ReleasePool(alloc1.PoolID)
assert.Check(t, is.ErrorContains(err, ""))
}
@ -137,11 +138,11 @@ func TestAddReleasePoolID(t *testing.T) {
t.Fatal(err)
}
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
alloc1, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8"})
if err != nil {
t.Fatalf("Unexpected failure in adding pool: %v", err)
}
k0, err := PoolIDFromString(pid0)
k0, err := PoolIDFromString(alloc1.PoolID)
if err != nil {
t.Fatal(err)
}
@ -155,17 +156,17 @@ func TestAddReleasePoolID(t *testing.T) {
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
}
pid1, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
alloc2, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8", SubPool: "10.0.0.0/16"})
if err != nil {
t.Fatalf("Unexpected failure in adding sub pool: %v", err)
}
k1, err := PoolIDFromString(pid1)
k1, err := PoolIDFromString(alloc2.PoolID)
if err != nil {
t.Fatal(err)
}
if pid0 == pid1 {
t.Fatalf("Incorrect poolIDs returned %s, %s", pid0, pid1)
if alloc1.PoolID == alloc2.PoolID {
t.Fatalf("Incorrect poolIDs returned %s, %s", alloc1.PoolID, alloc2.PoolID)
}
aSpace, err = a.getAddrSpace(localAddressSpace, false)
@ -177,7 +178,7 @@ func TestAddReleasePoolID(t *testing.T) {
t.Errorf("Unexpected autoRelease value for %s: %v", k1, got)
}
_, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8", SubPool: "10.0.0.0/16"})
if err == nil {
t.Fatalf("Expected failure in adding sub pool: %v", err)
}
@ -191,7 +192,7 @@ func TestAddReleasePoolID(t *testing.T) {
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
}
if err := a.ReleasePool(pid1); err != nil {
if err := a.ReleasePool(alloc2.PoolID); err != nil {
t.Fatal(err)
}
@ -203,7 +204,7 @@ func TestAddReleasePoolID(t *testing.T) {
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
}
if err := a.ReleasePool(pid0); err != nil {
if err := a.ReleasePool(alloc1.PoolID); err != nil {
t.Error(err)
}
@ -211,12 +212,12 @@ func TestAddReleasePoolID(t *testing.T) {
t.Error("Pool should have been deleted when released")
}
pid00, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
alloc10, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8"})
if err != nil {
t.Errorf("Unexpected failure in adding pool: %v", err)
}
if pid00 != pid0 {
t.Errorf("main pool should still exist. Got poolID %q, want %q", pid00, pid0)
if alloc10.PoolID != alloc1.PoolID {
t.Errorf("main pool should still exist. Got poolID %q, want %q", alloc10.PoolID, alloc1.PoolID)
}
aSpace, err = a.getAddrSpace(localAddressSpace, false)
@ -228,7 +229,7 @@ func TestAddReleasePoolID(t *testing.T) {
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
}
if err := a.ReleasePool(pid00); err != nil {
if err := a.ReleasePool(alloc10.PoolID); err != nil {
t.Error(err)
}
@ -241,7 +242,7 @@ func TestAddReleasePoolID(t *testing.T) {
t.Errorf("Base pool %s is still present: %v", k0, bp)
}
_, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8"})
if err != nil {
t.Errorf("Unexpected failure in adding pool: %v", err)
}
@ -260,25 +261,25 @@ func TestPredefinedPool(t *testing.T) {
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
pid, nw, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
alloc1, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace})
if err != nil {
t.Fatal(err)
}
pid2, nw2, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
alloc2, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace})
if err != nil {
t.Fatal(err)
}
if types.CompareIPNet(nw, nw2) {
t.Fatalf("Unexpected default network returned: %s = %s", nw2, nw)
if alloc1.Pool == alloc2.Pool {
t.Fatalf("Unexpected default network returned: %s = %s", alloc2.Pool, alloc1.Pool)
}
if err := a.ReleasePool(pid); err != nil {
if err := a.ReleasePool(alloc1.PoolID); err != nil {
t.Fatal(err)
}
if err := a.ReleasePool(pid2); err != nil {
if err := a.ReleasePool(alloc2.PoolID); err != nil {
t.Fatal(err)
}
}
@ -287,32 +288,29 @@ func TestRemoveSubnet(t *testing.T) {
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
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", true},
{globalAddressSpace, "172.17.0.0/16", false},
{globalAddressSpace, "10.0.0.0/8", false},
{globalAddressSpace, "2001:db8:1:2:3:4:5::/112", true},
{globalAddressSpace, "2001:db8:1:2:3:4:ffff::/112", true},
reqs := []ipamapi.PoolRequest{
{AddressSpace: localAddressSpace, Pool: "192.168.0.0/16"},
{AddressSpace: localAddressSpace, Pool: "172.17.0.0/16"},
{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8"},
{AddressSpace: localAddressSpace, Pool: "2001:db8:1:2:3:4:ffff::/112", V6: true},
{AddressSpace: globalAddressSpace, Pool: "172.17.0.0/16"},
{AddressSpace: globalAddressSpace, Pool: "10.0.0.0/8"},
{AddressSpace: globalAddressSpace, Pool: "2001:db8:1:2:3:4:5::/112", V6: true},
{AddressSpace: globalAddressSpace, Pool: "2001:db8:1:2:3:4:ffff::/112", V6: true},
}
allocs := make([]ipamapi.AllocatedPool, 0, len(reqs))
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 {
for _, req := range reqs {
alloc, err := a.RequestPool(req)
if err != nil {
t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error())
}
allocs = append(allocs, alloc)
}
for ind, id := range poolIDs {
if err := a.ReleasePool(id); err != nil {
t.Fatalf("Failed to release poolID %s (%d)", id, ind)
for idx, alloc := range allocs {
if err := a.ReleasePool(alloc.PoolID); err != nil {
t.Fatalf("Failed to release poolID %s (%d)", alloc.PoolID, idx)
}
}
}
@ -321,18 +319,18 @@ func TestGetSameAddress(t *testing.T) {
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
pid, _, _, err := a.RequestPool(localAddressSpace, "192.168.100.0/24", "", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "192.168.100.0/24"})
if err != nil {
t.Fatal(err)
}
ip := net.ParseIP("192.168.100.250")
_, _, err = a.RequestAddress(pid, ip, nil)
_, _, err = a.RequestAddress(alloc.PoolID, ip, nil)
if err != nil {
t.Fatal(err)
}
_, _, err = a.RequestAddress(pid, ip, nil)
_, _, err = a.RequestAddress(alloc.PoolID, ip, nil)
if err == nil {
t.Fatal(err)
}
@ -343,15 +341,14 @@ func TestPoolAllocationReuse(t *testing.T) {
assert.NilError(t, err)
// First get all pools until they are exhausted to
pList := []string{}
pool, _, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
allocs := []ipamapi.AllocatedPool{}
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace})
for err == nil {
pList = append(pList, pool)
pool, _, _, err = a.RequestPool(localAddressSpace, "", "", nil, false)
allocs = append(allocs, alloc)
alloc, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace})
}
nPools := len(pList)
for _, pool := range pList {
if err := a.ReleasePool(pool); err != nil {
for _, alloc := range allocs {
if err := a.ReleasePool(alloc.PoolID); err != nil {
t.Fatal(err)
}
}
@ -360,16 +357,16 @@ func TestPoolAllocationReuse(t *testing.T) {
// 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)
for i := 0; i < len(allocs); i++ {
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace})
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())
if _, ok := seen[alloc.Pool.String()]; ok {
t.Fatalf("Network %s was reused before exhausing the pool list", alloc.Pool.String())
}
seen[nw.String()] = true
if err := a.ReleasePool(pool); err != nil {
seen[alloc.Pool.String()] = true
if err := a.ReleasePool(alloc.PoolID); err != nil {
t.Fatal(err)
}
}
@ -380,12 +377,12 @@ func TestGetAddressSubPoolEqualPool(t *testing.T) {
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)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "172.18.0.0/16", SubPool: "172.18.0.0/16"})
if err != nil {
t.Fatal(err)
}
_, _, err = a.RequestAddress(pid, nil, nil)
_, _, err = a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -395,7 +392,7 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
poolID, _, _, err := a.RequestPool(localAddressSpace, "172.28.0.0/16", "172.28.30.0/24", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "172.28.0.0/16", SubPool: "172.28.30.0/24"})
if err != nil {
t.Fatal(err)
}
@ -404,7 +401,7 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
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 {
if c, _, err = a.RequestAddress(alloc.PoolID, nil, nil); err == nil {
ip = c
}
}
@ -415,28 +412,28 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
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 {
if err = a.ReleaseAddress(alloc.PoolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil {
if ip, _, err = a.RequestAddress(alloc.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(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8", SubPool: "10.0.0.0/16"})
if err != nil {
t.Fatal(err)
}
poolID, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/16", "10.0.0.0/24", nil, false)
alloc, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/16", SubPool: "10.0.0.0/24"})
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 {
if c, _, err = a.RequestAddress(alloc.PoolID, nil, nil); err == nil {
ip = c
}
}
@ -447,10 +444,10 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
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 {
if err = a.ReleaseAddress(alloc.PoolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil {
if ip, _, err = a.RequestAddress(alloc.PoolID, nil, nil); err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(rp, ip) {
@ -462,10 +459,10 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
dueExp, _ := types.ParseCIDR("10.2.2.2/16")
treExp, _ := types.ParseCIDR("10.2.2.1/16")
if poolID, _, _, err = a.RequestPool(localAddressSpace, "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
if alloc, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.2.0.0/16", SubPool: "10.2.2.0/24"}); err != nil {
t.Fatal(err)
}
tre, _, err := a.RequestAddress(poolID, treExp.IP, nil)
tre, _, err := a.RequestAddress(alloc.PoolID, treExp.IP, nil)
if err != nil {
t.Fatal(err)
}
@ -473,7 +470,7 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
t.Fatalf("Unexpected address: want %v, got %v", treExp, tre)
}
uno, _, err := a.RequestAddress(poolID, nil, nil)
uno, _, err := a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -481,7 +478,7 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
t.Fatalf("Unexpected address: %v", uno)
}
due, _, err := a.RequestAddress(poolID, nil, nil)
due, _, err := a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -489,10 +486,10 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
t.Fatalf("Unexpected address: %v", due)
}
if err = a.ReleaseAddress(poolID, uno.IP); err != nil {
if err = a.ReleaseAddress(alloc.PoolID, uno.IP); err != nil {
t.Fatal(err)
}
uno, _, err = a.RequestAddress(poolID, nil, nil)
uno, _, err = a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -500,10 +497,10 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
t.Fatalf("Unexpected address: %v", uno)
}
if err = a.ReleaseAddress(poolID, tre.IP); err != nil {
if err = a.ReleaseAddress(alloc.PoolID, tre.IP); err != nil {
t.Fatal(err)
}
tre, _, err = a.RequestAddress(poolID, nil, nil)
tre, _, err = a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -519,7 +516,7 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
poolID, _, _, err := a.RequestPool(localAddressSpace, "172.28.0.0/16", "172.28.30.0/24", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "172.28.0.0/16", SubPool: "172.28.30.0/24"})
if err != nil {
t.Fatal(err)
}
@ -528,7 +525,7 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
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 {
if c, _, err = a.RequestAddress(alloc.PoolID, nil, opts); err == nil {
ip = c
}
}
@ -539,28 +536,28 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
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 {
if err = a.ReleaseAddress(alloc.PoolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, opts); err != nil {
if ip, _, err = a.RequestAddress(alloc.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(localAddressSpace, "10.0.0.0/8", "10.0.0.0/16", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/8", SubPool: "10.0.0.0/16"})
if err != nil {
t.Fatal(err)
}
poolID, _, _, err = a.RequestPool(localAddressSpace, "10.0.0.0/16", "10.0.0.0/24", nil, false)
alloc, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.0.0.0/16", SubPool: "10.0.0.0/24"})
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 {
if c, _, err = a.RequestAddress(alloc.PoolID, nil, opts); err == nil {
ip = c
}
}
@ -571,10 +568,10 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
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 {
if err = a.ReleaseAddress(alloc.PoolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, opts); err != nil {
if ip, _, err = a.RequestAddress(alloc.PoolID, nil, opts); err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(rp, ip) {
@ -587,10 +584,10 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
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(localAddressSpace, "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
if alloc, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "10.2.0.0/16", SubPool: "10.2.2.0/24"}); err != nil {
t.Fatal(err)
}
tre, _, err := a.RequestAddress(poolID, treExp.IP, opts)
tre, _, err := a.RequestAddress(alloc.PoolID, treExp.IP, opts)
if err != nil {
t.Fatal(err)
}
@ -598,7 +595,7 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
t.Fatalf("Unexpected address: want %v, got %v", treExp, tre)
}
uno, _, err := a.RequestAddress(poolID, nil, opts)
uno, _, err := a.RequestAddress(alloc.PoolID, nil, opts)
if err != nil {
t.Fatal(err)
}
@ -606,7 +603,7 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
t.Fatalf("Unexpected address: %v", uno)
}
due, _, err := a.RequestAddress(poolID, nil, opts)
due, _, err := a.RequestAddress(alloc.PoolID, nil, opts)
if err != nil {
t.Fatal(err)
}
@ -614,10 +611,10 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
t.Fatalf("Unexpected address: %v", due)
}
if err = a.ReleaseAddress(poolID, uno.IP); err != nil {
if err = a.ReleaseAddress(alloc.PoolID, uno.IP); err != nil {
t.Fatal(err)
}
uno, _, err = a.RequestAddress(poolID, nil, opts)
uno, _, err = a.RequestAddress(alloc.PoolID, nil, opts)
if err != nil {
t.Fatal(err)
}
@ -625,10 +622,10 @@ func TestSerializeRequestReleaseAddressFromSubPool(t *testing.T) {
t.Fatalf("Unexpected address: %v", uno)
}
if err = a.ReleaseAddress(poolID, tre.IP); err != nil {
if err = a.ReleaseAddress(alloc.PoolID, tre.IP); err != nil {
t.Fatal(err)
}
tre, _, err = a.RequestAddress(poolID, nil, opts)
tre, _, err = a.RequestAddress(alloc.PoolID, nil, opts)
if err != nil {
t.Fatal(err)
}
@ -659,22 +656,22 @@ func TestRequestSyntaxCheck(t *testing.T) {
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
_, _, _, err = a.RequestPool("", pool, "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{Pool: pool})
if err == nil {
t.Fatal("Failed to detect wrong request: empty address space")
}
_, _, _, err = a.RequestPool("", pool, subPool, nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{Pool: pool, SubPool: subPool})
if err == nil {
t.Fatal("Failed to detect wrong request: empty address space")
}
_, _, _, err = a.RequestPool(localAddressSpace, "", subPool, nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, SubPool: subPool})
if err == nil {
t.Fatal("Failed to detect wrong request: subPool specified and no pool")
}
pid, _, _, err := a.RequestPool(localAddressSpace, pool, subPool, nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: pool, SubPool: subPool})
if err != nil {
t.Fatalf("Unexpected failure: %v", err)
}
@ -685,13 +682,13 @@ func TestRequestSyntaxCheck(t *testing.T) {
}
ip := net.ParseIP("172.17.0.23")
_, _, err = a.RequestAddress(pid, ip, nil)
_, _, err = a.RequestAddress(alloc.PoolID, 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)
_, _, err = a.RequestAddress(alloc.PoolID, ip, nil)
if err != nil {
t.Fatalf("Unexpected failure: %v", err)
}
@ -701,14 +698,14 @@ func TestRequestSyntaxCheck(t *testing.T) {
t.Fatal("Failed to detect wrong request: no pool id specified")
}
err = a.ReleaseAddress(pid, nil)
err = a.ReleaseAddress(alloc.PoolID, nil)
if err == nil {
t.Fatal("Failed to detect wrong request: no pool id specified")
}
err = a.ReleaseAddress(pid, ip)
err = a.ReleaseAddress(alloc.PoolID, ip)
if err != nil {
t.Fatalf("Unexpected failure: %v: %s, %s", err, pid, ip)
t.Fatalf("Unexpected failure: %v: %s, %s", err, alloc.PoolID, ip)
}
}
@ -807,12 +804,12 @@ func TestOverlappingRequests(t *testing.T) {
// Set up some existing allocations. This should always succeed.
for _, env := range tc.environment {
_, _, _, err = a.RequestPool(localAddressSpace, env, "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: env})
assert.NilError(t, err)
}
// Make the test allocation.
_, _, _, err = a.RequestPool(localAddressSpace, tc.subnet, "", nil, false)
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: tc.subnet})
if tc.ok {
assert.NilError(t, err)
} else {
@ -848,7 +845,7 @@ func TestUnusualSubnets(t *testing.T) {
// IPv4 /31 blocks. See RFC 3021.
//
pool, _, _, err := allocator.RequestPool(localAddressSpace, subnet, "", nil, false)
alloc, err := allocator.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: subnet})
if err != nil {
t.Fatal(err)
}
@ -856,7 +853,7 @@ func TestUnusualSubnets(t *testing.T) {
// Outside-the-range
for _, outside := range outsideTheRangeAddresses {
_, _, errx := allocator.RequestAddress(pool, net.ParseIP(outside.address), nil)
_, _, errx := allocator.RequestAddress(alloc.PoolID, net.ParseIP(outside.address), nil)
if errx != ipamapi.ErrIPOutOfRange {
t.Fatalf("Address %s failed to throw expected error: %s", outside.address, errx.Error())
}
@ -865,7 +862,7 @@ func TestUnusualSubnets(t *testing.T) {
// Should get just these two IPs followed by exhaustion on the next request
for _, expected := range expectedAddresses {
got, _, errx := allocator.RequestAddress(pool, nil, nil)
got, _, errx := allocator.RequestAddress(alloc.PoolID, nil, nil)
if errx != nil {
t.Fatalf("Failed to obtain the address: %s", errx.Error())
}
@ -876,7 +873,7 @@ func TestUnusualSubnets(t *testing.T) {
}
}
_, _, err = allocator.RequestAddress(pool, nil, nil)
_, _, err = allocator.RequestAddress(alloc.PoolID, nil, nil)
if err != ipamapi.ErrNoAvailableIPs {
t.Fatal("Did not get expected error when pool is exhausted.")
}
@ -888,14 +885,14 @@ func TestRelease(t *testing.T) {
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: subnet})
if err != nil {
t.Fatal(err)
}
// Allocate all addresses
for err != ipamapi.ErrNoAvailableIPs {
_, _, err = a.RequestAddress(pid, nil, nil)
_, _, err = a.RequestAddress(alloc.PoolID, nil, nil)
}
toRelease := []struct {
@ -928,13 +925,13 @@ func TestRelease(t *testing.T) {
// 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)
a.ReleaseAddress(alloc.PoolID, ip0)
bm := a.local4.subnets[netip.MustParsePrefix(subnet)].addrs
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)
nw, _, err := a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatalf("Failed to obtain the address: %s", err.Error())
}
@ -988,7 +985,7 @@ func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP str
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
assert.NilError(t, err)
pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: subnet})
if err != nil {
t.Fatal(err)
}
@ -996,7 +993,7 @@ func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP str
i := 0
start := time.Now()
for ; i < numReq; i++ {
nw, _, err = a.RequestAddress(pid, nil, nil)
nw, _, err = a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -1011,9 +1008,9 @@ func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP str
}
func benchmarkRequest(b *testing.B, a *Allocator, subnet string) {
pid, _, _, err := a.RequestPool(localAddressSpace, subnet, "", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: subnet})
for err != ipamapi.ErrNoAvailableIPs {
_, _, err = a.RequestAddress(pid, nil, nil)
_, _, err = a.RequestAddress(alloc.PoolID, nil, nil)
}
}
@ -1046,7 +1043,7 @@ func testAllocateRandomDeallocate(t *testing.T, pool, subPool string, num int, s
t.Fatal(err)
}
pid, _, _, err := a.RequestPool(localAddressSpace, pool, subPool, nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: pool, SubPool: subPool})
if err != nil {
t.Fatal(err)
}
@ -1055,7 +1052,7 @@ func testAllocateRandomDeallocate(t *testing.T, pool, subPool string, num int, s
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)
ip, _, err := a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -1078,7 +1075,7 @@ func testAllocateRandomDeallocate(t *testing.T, pool, subPool string, num int, s
for i := 0; i < num/2; i++ {
idx := pattern[i]
ip := indices[idx]
err := a.ReleaseAddress(pid, ip.IP)
err := a.ReleaseAddress(alloc.PoolID, ip.IP)
if err != nil {
t.Fatalf("Unexpected failure on deallocation of %s: %v.\nSeed: %d.", ip, err, seed)
}
@ -1088,7 +1085,7 @@ func testAllocateRandomDeallocate(t *testing.T, pool, subPool string, num int, s
// Request a quarter of addresses
for i := 0; i < num/2; i++ {
ip, _, err := a.RequestAddress(pid, nil, nil)
ip, _, err := a.RequestAddress(alloc.PoolID, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -1148,7 +1145,8 @@ func runParallelTests(t *testing.T, instance int) {
defer done.Done()
}
_, pools[instance], _, err = allocator.RequestPool(localAddressSpace, "", "", nil, false)
alloc, err := allocator.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace})
pools[instance] = netiputil.ToIPNet(alloc.Pool)
if err != nil {
t.Fatal(err)
}
@ -1183,7 +1181,7 @@ func TestRequestReleaseAddressDuplicate(t *testing.T) {
}
var l sync.Mutex
poolID, _, _, err := a.RequestPool(localAddressSpace, "198.168.0.0/23", "", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: "198.168.0.0/23"})
if err != nil {
t.Fatal(err)
}
@ -1203,7 +1201,7 @@ outer:
break outer
default:
}
if c, _, err = a.RequestAddress(poolID, nil, opts); err == nil {
if c, _, err = a.RequestAddress(alloc.PoolID, nil, opts); err == nil {
break
}
// No addresses available. Spin until one is.
@ -1236,7 +1234,7 @@ outer:
l.Lock()
ips = append(ips, IP{ip, -1})
l.Unlock()
return a.ReleaseAddress(poolID, ip.IP)
return a.ReleaseAddress(alloc.PoolID, ip.IP)
})
}
}

View file

@ -45,7 +45,7 @@ func newTestContext(t *testing.T, mask int, options map[string]string) *testCont
// total ips 2^(32-mask) - 2 (network and broadcast)
totalIps := 1<<uint(32-mask) - 2
pid, _, _, err := a.RequestPool(localAddressSpace, network, "", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace, Pool: network})
if err != nil {
t.Fatal(err)
}
@ -55,7 +55,7 @@ func newTestContext(t *testing.T, mask int, options map[string]string) *testCont
opts: options,
ipList: make([]*net.IPNet, 0, totalIps),
ipMap: make(map[string]bool),
pid: pid,
pid: alloc.PoolID,
maxIP: totalIps,
}
}
@ -93,21 +93,21 @@ func TestRequestPoolParallel(t *testing.T) {
for i := 0; i < 120; i++ {
group.Go(func() error {
name, _, _, err := a.RequestPool("GlobalDefault", "", "", nil, false)
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: "GlobalDefault"})
if err != nil {
t.Log(err) // log so we can see the error in real time rather than at the end when we actually call "Wait".
return fmt.Errorf("request error %v", err)
}
idx := atomic.AddInt32(&operationIndex, 1)
ch <- &op{idx, true, name}
ch <- &op{idx, true, alloc.PoolID}
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
idx = atomic.AddInt32(&operationIndex, 1)
err = a.ReleasePool(name)
err = a.ReleasePool(alloc.PoolID)
if err != nil {
t.Log(err) // log so we can see the error in real time rather than at the end when we actually call "Wait".
return fmt.Errorf("release error %v", err)
}
ch <- &op{idx, false, name}
ch <- &op{idx, false, alloc.PoolID}
return nil
})
}

View file

@ -3,6 +3,7 @@ package ipamapi
import (
"net"
"net/netip"
"github.com/docker/docker/libnetwork/types"
)
@ -45,13 +46,9 @@ var (
type Ipam interface {
// GetDefaultAddressSpaces returns the default local and global address spaces for this ipam
GetDefaultAddressSpaces() (string, string, error)
// RequestPool returns an address pool along with its unique id. Address space is a mandatory field
// which denotes a set of non-overlapping pools. requestedPool describes the pool of addresses in CIDR notation.
// requestedSubPool indicates a smaller range of addresses from the pool, for now it is specified in CIDR notation.
// Both requestedPool and requestedSubPool are non-mandatory fields. When they are not specified, Ipam driver may choose to
// return a self chosen pool for this request. In such case the v6 flag needs to be set appropriately so
// that the driver would return the expected ip version pool.
RequestPool(addressSpace, requestedPool, requestedSubPool string, options map[string]string, v6 bool) (poolID string, pool *net.IPNet, meta map[string]string, err error)
// RequestPool allocate an address pool either statically or dynamically
// based on req.
RequestPool(req PoolRequest) (AllocatedPool, error)
// ReleasePool releases the address pool identified by the passed id
ReleasePool(poolID string) error
// RequestAddress request an address from the specified pool ID. Input options or required IP can be passed.
@ -63,6 +60,41 @@ type Ipam interface {
IsBuiltIn() bool
}
type PoolRequest struct {
// AddressSpace is a mandatory field which denotes which block of pools
// should be used to make the allocation. This value is opaque, and only
// the IPAM driver can interpret it. Each driver might support a different
// set of AddressSpace.
AddressSpace string
// Pool is a prefix in CIDR notation. It's non-mandatory. When specified
// the Pool will be statically allocated. The Pool is used for dynamic
// address allocation -- except when SubPool is specified.
Pool string
// SubPool is a subnet from Pool, in CIDR notation too. It's non-mandatory.
// When specified, it represents the subnet where addresses will be
// dynamically allocated. It can't be specified if Pool isn't specified.
SubPool string
// Options is a map of opaque k/v passed to the driver. It's non-mandatory.
// Drivers are free to ignore it.
Options map[string]string
// V6 indicates which address family should be used to dynamically allocate
// a prefix (ie. when Pool isn't specified).
V6 bool
}
type AllocatedPool struct {
// PoolID represents the ID of the allocated pool. Its value is opaque and
// shouldn't be interpreted by anything but the IPAM driver that generated
// it.
PoolID string
// Pool is the allocated prefix.
Pool netip.Prefix
// Meta represents a list of extra IP addresses automatically reserved
// during the pool allocation. These are generally keyed by well-known
// strings defined in the netlabel package.
Meta map[string]string
}
// Capability represents the requirements and capabilities of the IPAM driver
type Capability struct {
// Whether on address request, libnetwork must

View file

@ -4,6 +4,7 @@ package null
import (
"net"
"net/netip"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/types"
@ -15,7 +16,7 @@ const (
defaultPoolID = defaultAddressSpace + "/" + defaultPoolCIDR
)
var defaultPool, _ = types.ParseCIDR(defaultPoolCIDR)
var defaultPool = netip.MustParsePrefix(defaultPoolCIDR)
type allocator struct{}
@ -23,20 +24,23 @@ func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
return defaultAddressSpace, defaultAddressSpace, nil
}
func (a *allocator) RequestPool(addressSpace, requestedPool, requestedSubPool string, _ map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
if addressSpace != defaultAddressSpace {
return "", nil, nil, types.InvalidParameterErrorf("unknown address space: %s", addressSpace)
func (a *allocator) RequestPool(req ipamapi.PoolRequest) (ipamapi.AllocatedPool, error) {
if req.AddressSpace != defaultAddressSpace {
return ipamapi.AllocatedPool{}, types.InvalidParameterErrorf("unknown address space: %s", req.AddressSpace)
}
if requestedPool != "" {
return "", nil, nil, types.InvalidParameterErrorf("null ipam driver does not handle specific address pool requests")
if req.Pool != "" {
return ipamapi.AllocatedPool{}, types.InvalidParameterErrorf("null ipam driver does not handle specific address pool requests")
}
if requestedSubPool != "" {
return "", nil, nil, types.InvalidParameterErrorf("null ipam driver does not handle specific address subpool requests")
if req.SubPool != "" {
return ipamapi.AllocatedPool{}, types.InvalidParameterErrorf("null ipam driver does not handle specific address subpool requests")
}
if v6 {
return "", nil, nil, types.InvalidParameterErrorf("null ipam driver does not handle IPv6 address pool requests")
if req.V6 {
return ipamapi.AllocatedPool{}, types.InvalidParameterErrorf("null ipam driver does not handle IPv6 address pool requests")
}
return defaultPoolID, defaultPool, nil, nil
return ipamapi.AllocatedPool{
PoolID: defaultPoolID,
Pool: defaultPool,
}, nil
}
func (a *allocator) ReleasePool(poolID string) error {

View file

@ -3,42 +3,30 @@ package null
import (
"testing"
"github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/libnetwork/ipamapi"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestPoolRequest(t *testing.T) {
a := allocator{}
pid, pool, _, err := a.RequestPool(defaultAddressSpace, "", "", nil, false)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(defaultPool, pool) {
t.Fatalf("Unexpected pool returned. Expected %v. Got: %v", defaultPool, pool)
}
if pid != defaultPoolID {
t.Fatalf("Unexpected pool id returned. Expected: %s. Got: %s", defaultPoolID, pid)
}
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: defaultAddressSpace})
assert.NilError(t, err)
assert.Check(t, is.Equal(alloc.PoolID, defaultPoolID))
assert.Check(t, is.Equal(alloc.Pool, defaultPool))
_, _, _, err = a.RequestPool("default", "", "", nil, false)
if err == nil {
t.Fatal("Unexpected success")
}
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: "default"})
assert.ErrorContains(t, err, "unknown address space: default")
_, _, _, err = a.RequestPool(defaultAddressSpace, "192.168.0.0/16", "", nil, false)
if err == nil {
t.Fatal("Unexpected success")
}
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: defaultAddressSpace, Pool: "192.168.0.0/16"})
assert.ErrorContains(t, err, "null ipam driver does not handle specific address pool requests")
_, _, _, err = a.RequestPool(defaultAddressSpace, "", "192.168.0.0/24", nil, false)
if err == nil {
t.Fatal("Unexpected success")
}
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: defaultAddressSpace, SubPool: "192.168.0.0/24"})
assert.ErrorContains(t, err, "null ipam driver does not handle specific address subpool requests")
_, _, _, err = a.RequestPool(defaultAddressSpace, "", "", nil, true)
if err == nil {
t.Fatal("Unexpected success")
}
_, err = a.RequestPool(ipamapi.PoolRequest{AddressSpace: defaultAddressSpace, V6: true})
assert.ErrorContains(t, err, "null ipam driver does not handle IPv6 address pool requests")
}
func TestOtherRequests(t *testing.T) {

View file

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net"
"net/netip"
"github.com/containerd/log"
"github.com/docker/docker/libnetwork/ipamapi"
@ -116,14 +117,25 @@ func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
}
// RequestPool requests an address pool in the specified address space
func (a *allocator) RequestPool(addressSpace, requestedPool, requestedSubPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
req := &api.RequestPoolRequest{AddressSpace: addressSpace, Pool: requestedPool, SubPool: requestedSubPool, Options: options, V6: v6}
res := &api.RequestPoolResponse{}
if err := a.call("RequestPool", req, res); err != nil {
return "", nil, nil, err
func (a *allocator) RequestPool(req ipamapi.PoolRequest) (ipamapi.AllocatedPool, error) {
remoteReq := &api.RequestPoolRequest{
AddressSpace: req.AddressSpace,
Pool: req.Pool,
SubPool: req.SubPool,
Options: req.Options,
V6: req.V6,
}
retPool, err := types.ParseCIDR(res.Pool)
return res.PoolID, retPool, res.Data, err
res := &api.RequestPoolResponse{}
if err := a.call("RequestPool", remoteReq, res); err != nil {
return ipamapi.AllocatedPool{}, err
}
retPool, err := netip.ParsePrefix(res.Pool)
return ipamapi.AllocatedPool{
PoolID: res.PoolID,
Pool: retPool,
Meta: res.Data,
}, err
}
// ReleasePool removes an address pool from the specified address space

View file

@ -14,6 +14,8 @@ import (
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/pkg/plugins"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func decodeToMap(r *http.Request) (res map[string]interface{}, err error) {
@ -248,72 +250,51 @@ func TestRemoteDriver(t *testing.T) {
d := newAllocator(plugin, client)
l, g, err := d.(*allocator).GetDefaultAddressSpaces()
if err != nil {
t.Fatal(err)
}
if l != "white" || g != "blue" {
t.Fatalf("Unexpected default local/global address spaces: %s, %s", l, g)
}
assert.NilError(t, err)
assert.Check(t, is.Equal(l, "white"))
assert.Check(t, is.Equal(g, "blue"))
// Request any pool
poolID, pool, _, err := d.RequestPool("white", "", "", nil, false)
if err != nil {
t.Fatal(err)
}
if poolID != "white/172.18.0.0/16" {
t.Fatalf("Unexpected pool id: %s", poolID)
}
if pool == nil || pool.String() != "172.18.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool)
}
alloc, err := d.RequestPool(ipamapi.PoolRequest{
AddressSpace: "white",
})
assert.NilError(t, err)
assert.Check(t, is.Equal(alloc.PoolID, "white/172.18.0.0/16"))
assert.Check(t, is.Equal(alloc.Pool.String(), "172.18.0.0/16"))
// Request specific pool
poolID2, pool2, ops, err := d.RequestPool("white", "172.20.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
if poolID2 != "white/172.20.0.0/16" {
t.Fatalf("Unexpected pool id: %s", poolID2)
}
if pool2 == nil || pool2.String() != "172.20.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool2)
}
if dns, ok := ops["DNS"]; !ok || dns != "8.8.8.8" {
t.Fatal("Missing options")
}
alloc, err = d.RequestPool(ipamapi.PoolRequest{
AddressSpace: "white",
Pool: "172.20.0.0/16",
})
assert.NilError(t, err)
assert.Check(t, is.Equal(alloc.PoolID, "white/172.20.0.0/16"))
assert.Check(t, is.Equal(alloc.Pool.String(), "172.20.0.0/16"))
assert.Check(t, is.Equal(alloc.Meta["DNS"], "8.8.8.8"))
// Request specific pool and subpool
poolID3, pool3, _, err := d.RequestPool("white", "172.20.0.0/16", "172.20.3.0/24" /*nil*/, map[string]string{"culo": "yes"}, false)
if err != nil {
t.Fatal(err)
}
if poolID3 != "white/172.20.0.0/16/172.20.3.0/24" {
t.Fatalf("Unexpected pool id: %s", poolID3)
}
if pool3 == nil || pool3.String() != "172.20.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool3)
}
alloc, err = d.RequestPool(ipamapi.PoolRequest{
AddressSpace: "white",
Pool: "172.20.0.0/16",
SubPool: "172.20.3.0/24",
Options: map[string]string{"culo": "yes"},
})
assert.NilError(t, err)
assert.Check(t, is.Equal(alloc.PoolID, "white/172.20.0.0/16/172.20.3.0/24"))
assert.Check(t, is.Equal(alloc.Pool.String(), "172.20.0.0/16"))
// Request any address
addr, _, err := d.RequestAddress(poolID2, nil, nil)
if err != nil {
t.Fatal(err)
}
if addr == nil || addr.String() != "172.20.0.34/16" {
t.Fatalf("Unexpected address: %s", addr)
}
addr, _, err := d.RequestAddress("white/172.20.0.0/16", nil, nil)
assert.NilError(t, err)
assert.Check(t, is.Equal(addr.String(), "172.20.0.34/16"))
// Request specific address
addr2, _, err := d.RequestAddress(poolID2, net.ParseIP("172.20.1.45"), nil)
if err != nil {
t.Fatal(err)
}
if addr2 == nil || addr2.String() != "172.20.1.45/16" {
t.Fatalf("Unexpected address: %s", addr2)
}
addr2, _, err := d.RequestAddress("white/172.20.0.0/16", net.ParseIP("172.20.1.45"), nil)
assert.NilError(t, err)
assert.Check(t, is.Equal(addr2.String(), "172.20.1.45/16"))
// Release address
err = d.ReleaseAddress(poolID, net.ParseIP("172.18.1.45"))
err = d.ReleaseAddress("white/172.20.0.0/16", net.ParseIP("172.18.1.45"))
if err != nil {
t.Fatal(err)
}

View file

@ -2,7 +2,9 @@ package windowsipam
import (
"context"
"fmt"
"net"
"net/netip"
"github.com/containerd/log"
"github.com/docker/docker/libnetwork/ipamapi"
@ -17,7 +19,7 @@ const (
// DefaultIPAM defines the default ipam-driver for local-scoped windows networks
const DefaultIPAM = "windows"
var defaultPool, _ = types.ParseCIDR("0.0.0.0/0")
var defaultPool = netip.MustParsePrefix("0.0.0.0/0")
type allocator struct{}
@ -32,25 +34,24 @@ func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
// RequestPool returns an address pool along with its unique id. This is a null ipam driver. It allocates the
// subnet user asked and does not validate anything. Doesn't support subpool allocation
func (a *allocator) RequestPool(addressSpace, requestedPool, requestedSubPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
log.G(context.TODO()).Debugf("RequestPool(%s, %s, %s, %v, %t)", addressSpace, requestedPool, requestedSubPool, options, v6)
if requestedSubPool != "" || v6 {
return "", nil, nil, types.InternalErrorf("This request is not supported by null ipam driver")
func (a *allocator) RequestPool(req ipamapi.PoolRequest) (ipamapi.AllocatedPool, error) {
log.G(context.TODO()).Debugf("RequestPool: %+v", req)
if req.SubPool != "" || req.V6 {
return ipamapi.AllocatedPool{}, types.InternalErrorf("this request is not supported by the 'windows' ipam driver")
}
var ipNet *net.IPNet
var err error
if requestedPool != "" {
_, ipNet, err = net.ParseCIDR(requestedPool)
if err != nil {
return "", nil, nil, err
pool := defaultPool
if req.Pool != "" {
var err error
if pool, err = netip.ParsePrefix(req.Pool); err != nil {
return ipamapi.AllocatedPool{}, fmt.Errorf("invalid IPAM request: %w", err)
}
} else {
ipNet = defaultPool
}
return ipNet.String(), ipNet, nil, nil
return ipamapi.AllocatedPool{
PoolID: pool.String(),
Pool: pool,
}, nil
}
// ReleasePool releases the address pool - always succeeds

View file

@ -7,41 +7,42 @@ import (
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/types"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestWindowsIPAM(t *testing.T) {
a := &allocator{}
alloc, err := a.RequestPool(ipamapi.PoolRequest{AddressSpace: localAddressSpace})
assert.NilError(t, err)
assert.Check(t, is.Equal(alloc.PoolID, defaultPool.String()))
assert.Check(t, is.Equal(alloc.Pool, defaultPool))
alloc, err = a.RequestPool(ipamapi.PoolRequest{
AddressSpace: localAddressSpace,
Pool: "192.168.0.0/16",
})
assert.NilError(t, err)
assert.Check(t, is.Equal(alloc.PoolID, "192.168.0.0/16"))
assert.Check(t, is.Equal(alloc.Pool.String(), "192.168.0.0/16"))
_, err = a.RequestPool(ipamapi.PoolRequest{
AddressSpace: localAddressSpace,
Pool: "192.168.0.0/16",
SubPool: "192.168.0.0/16",
})
assert.ErrorContains(t, err, "this request is not supported by the 'windows' ipam driver")
_, err = a.RequestPool(ipamapi.PoolRequest{
AddressSpace: localAddressSpace,
V6: true,
})
assert.ErrorContains(t, err, "this request is not supported by the 'windows' ipam driver")
requestPool, _ := types.ParseCIDR("192.168.0.0/16")
requestAddress := net.ParseIP("192.168.1.1")
pid, pool, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(defaultPool, pool) ||
pid != pool.String() {
t.Fatalf("Unexpected data returned. Expected %v : %s. Got: %v : %s", defaultPool, pid, pool, pool.String())
}
pid, pool, _, err = a.RequestPool(localAddressSpace, requestPool.String(), "", nil, false)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(requestPool, pool) ||
pid != requestPool.String() {
t.Fatalf("Unexpected data returned. Expected %v : %s. Got: %v : %s", requestPool, requestPool.String(), pool, pool.String())
}
_, _, _, err = a.RequestPool(localAddressSpace, requestPool.String(), requestPool.String(), nil, false)
if err == nil {
t.Fatal("Unexpected success for subpool request")
}
_, _, _, err = a.RequestPool(localAddressSpace, requestPool.String(), "", nil, true)
if err == nil {
t.Fatal("Unexpected success for v6 request")
}
err = a.ReleasePool(requestPool.String())
if err != nil {
t.Fatal(err)

View file

@ -17,6 +17,7 @@ import (
"github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/etchosts"
"github.com/docker/docker/libnetwork/internal/netiputil"
"github.com/docker/docker/libnetwork/internal/setmatrix"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/netlabel"
@ -1508,7 +1509,7 @@ func (n *Network) ipamAllocate() error {
return err
}
func (n *Network) requestPoolHelper(ipam ipamapi.Ipam, addressSpace, requestedPool, requestedSubPool string, options map[string]string, v6 bool) (poolID string, pool *net.IPNet, meta map[string]string, err error) {
func (n *Network) requestPoolHelper(ipam ipamapi.Ipam, addressSpace, requestedPool, requestedSubPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
var tmpPoolLeases []string
defer func() {
// Prevent repeated lock/unlock in the loop.
@ -1516,13 +1517,19 @@ func (n *Network) requestPoolHelper(ipam ipamapi.Ipam, addressSpace, requestedPo
// Release all pools we held on to.
for _, pID := range tmpPoolLeases {
if err := ipam.ReleasePool(pID); err != nil {
log.G(context.TODO()).Warnf("Failed to release overlapping pool %s while returning from pool request helper for network %s", pool, nwName)
log.G(context.TODO()).Warnf("Failed to release overlapping pool while returning from pool request helper for network %s", nwName)
}
}
}()
for {
poolID, pool, meta, err = ipam.RequestPool(addressSpace, requestedPool, requestedSubPool, options, v6)
alloc, err := ipam.RequestPool(ipamapi.PoolRequest{
AddressSpace: addressSpace,
Pool: requestedPool,
SubPool: requestedSubPool,
Options: options,
V6: v6,
})
if err != nil {
return "", nil, nil, err
}
@ -1544,13 +1551,13 @@ func (n *Network) requestPoolHelper(ipam ipamapi.Ipam, addressSpace, requestedPo
// instead would likely have avoided that as well, so we can only guess.
//
// [1]: https://github.com/moby/libnetwork/commit/5ca79d6b87873264516323a7b76f0af7d0298492#diff-bdcd879439d041827d334846f9aba01de6e3683ed8fdd01e63917dae6df23846
if requestedPool != "" || n.Scope() == scope.Global || pool.String() == "0.0.0.0/0" {
return poolID, pool, meta, nil
if requestedPool != "" || n.Scope() == scope.Global || alloc.Pool.String() == "0.0.0.0/0" {
return alloc.PoolID, netiputil.ToIPNet(alloc.Pool), alloc.Meta, nil
}
// Check for overlap and if none found, we have found the right pool.
if _, err := netutils.FindAvailableNetwork([]*net.IPNet{pool}); err == nil {
return poolID, pool, meta, nil
if _, err := netutils.FindAvailableNetwork([]*net.IPNet{netiputil.ToIPNet(alloc.Pool)}); err == nil {
return alloc.PoolID, netiputil.ToIPNet(alloc.Pool), alloc.Meta, nil
}
// Pool obtained in this iteration is overlapping. Hold onto the pool
@ -1558,7 +1565,7 @@ func (n *Network) requestPoolHelper(ipam ipamapi.Ipam, addressSpace, requestedPo
// the same pool over again. But make sure we still do a deferred release
// when we have either obtained a non-overlapping pool or ran out of
// pre-defined pools.
tmpPoolLeases = append(tmpPoolLeases, poolID)
tmpPoolLeases = append(tmpPoolLeases, alloc.PoolID)
}
}