libnet/ipam: simplify the data model
The address spaces are orthogonal. There is no shared state between them logically so there is no reason for them to share any in-memory data structures. addrSpace is responsible for allocating subnets and addresses, while Allocator is responsible for implementing the IPAM API. Lower all the implementation details of allocation into addrSpace. There is no longer a need to include the name of the address space in the map keys for subnets now that each addrSpace holds its own state independently from other addrSpaces. Remove the AddressSpace field from the struct used for map keys within an addrSpace so that an addrSpace does not need to know its own name. Pool allocations were encoded in a tree structure, using parent references and reference counters. This structure affords for pools subdivided an arbitrary number of times to be modeled, in theory. In practice, the max depth is only two: master pools and sub-pools. The allocator data model has also been heavily influenced by the requirements and limitations of Datastore persistence, which are no longer applicable. Address allocations are always associated with a master pool. Sub-pools only serve to restrict the range of addresses within the master pool which could be allocated from. Model pool allocations within an address space as a two-level hierarchy of maps. Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
parent
8273db28f3
commit
01dbe23b6f
4 changed files with 223 additions and 277 deletions
|
@ -3,9 +3,9 @@ package ipam
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/libnetwork/bitseq"
|
||||
"github.com/docker/docker/libnetwork/bitmap"
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -20,27 +20,19 @@ const (
|
|||
type Allocator struct {
|
||||
// The address spaces
|
||||
local, global *addrSpace
|
||||
// Allocated addresses in each address space's subnet
|
||||
addresses map[SubnetKey]*bitseq.Handle
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewAllocator returns an instance of libnetwork ipam
|
||||
func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) {
|
||||
a := &Allocator{
|
||||
addresses: map[SubnetKey]*bitseq.Handle{},
|
||||
}
|
||||
|
||||
a.local = newAddrSpace(a, lcAs)
|
||||
a.global = newAddrSpace(a, glAs)
|
||||
|
||||
return a, nil
|
||||
return &Allocator{
|
||||
local: newAddrSpace(lcAs),
|
||||
global: newAddrSpace(glAs),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newAddrSpace(a *Allocator, predefined []*net.IPNet) *addrSpace {
|
||||
func newAddrSpace(predefined []*net.IPNet) *addrSpace {
|
||||
return &addrSpace{
|
||||
subnets: map[SubnetKey]*PoolData{},
|
||||
alloc: a,
|
||||
subnets: map[string]*PoolData{},
|
||||
predefined: predefined,
|
||||
}
|
||||
}
|
||||
|
@ -69,45 +61,38 @@ func (a *Allocator) RequestPool(addressSpace, pool, subPool string, options map[
|
|||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
k := SubnetKey{AddressSpace: addressSpace}
|
||||
|
||||
if pool == "" && subPool != "" {
|
||||
return parseErr(ipamapi.ErrInvalidSubPool)
|
||||
}
|
||||
pdf := pool == ""
|
||||
|
||||
var (
|
||||
nw *net.IPNet
|
||||
ipr *AddressRange
|
||||
)
|
||||
if !pdf {
|
||||
if _, nw, err = net.ParseCIDR(pool); err != nil {
|
||||
return parseErr(ipamapi.ErrInvalidPool)
|
||||
}
|
||||
k.Subnet = nw.String()
|
||||
k.ChildSubnet = subPool
|
||||
k := PoolID{AddressSpace: addressSpace}
|
||||
|
||||
if pool == "" {
|
||||
if subPool != "" {
|
||||
if ipr, err = getAddressRange(subPool, nw); err != nil {
|
||||
return parseErr(err)
|
||||
}
|
||||
return parseErr(ipamapi.ErrInvalidSubPool)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
retry:
|
||||
if pdf {
|
||||
if nw, err = a.getPredefinedPool(addressSpace, v6); err != nil {
|
||||
var nw *net.IPNet
|
||||
nw, k.SubnetKey, err = aSpace.allocatePredefinedPool(v6)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
k.Subnet = nw.String()
|
||||
return k.String(), nw, nil, nil
|
||||
}
|
||||
|
||||
if err := aSpace.allocateSubnet(k, nw, ipr, pdf); err != nil {
|
||||
if _, ok := err.(types.MaskableError); ok {
|
||||
logrus.Debugf("Retrying predefined pool search: %v", err)
|
||||
goto retry
|
||||
var (
|
||||
nw, sub *net.IPNet
|
||||
)
|
||||
if _, nw, err = net.ParseCIDR(pool); err != nil {
|
||||
return parseErr(ipamapi.ErrInvalidPool)
|
||||
}
|
||||
|
||||
if subPool != "" {
|
||||
var err error
|
||||
_, sub, err = net.ParseCIDR(subPool)
|
||||
if err != nil {
|
||||
return parseErr(ipamapi.ErrInvalidSubPool)
|
||||
}
|
||||
k.ChildSubnet = subPool
|
||||
}
|
||||
|
||||
k.SubnetKey, err = aSpace.allocateSubnet(nw, sub)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
|
@ -117,7 +102,7 @@ retry:
|
|||
// ReleasePool releases the address pool identified by the passed id
|
||||
func (a *Allocator) ReleasePool(poolID string) error {
|
||||
logrus.Debugf("ReleasePool(%s)", poolID)
|
||||
k := SubnetKey{}
|
||||
k := PoolID{}
|
||||
if err := k.FromString(poolID); err != nil {
|
||||
return types.BadRequestErrorf("invalid pool id: %s", poolID)
|
||||
}
|
||||
|
@ -127,14 +112,12 @@ func (a *Allocator) ReleasePool(poolID string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return aSpace.releaseSubnet(k)
|
||||
return aSpace.releaseSubnet(k.SubnetKey)
|
||||
}
|
||||
|
||||
// Given the address space, returns the local or global PoolConfig based on whether the
|
||||
// address space is local or global. AddressSpace locality is registered with IPAM out of band.
|
||||
func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
switch as {
|
||||
case localAddressSpace:
|
||||
return a.local, nil
|
||||
|
@ -144,9 +127,7 @@ func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) {
|
|||
return nil, types.BadRequestErrorf("cannot find address space %s", as)
|
||||
}
|
||||
|
||||
func (a *Allocator) insertBitMask(key SubnetKey, pool *net.IPNet) error {
|
||||
//logrus.Debugf("Inserting bitmask (%s, %s)", key.String(), pool.String())
|
||||
|
||||
func newPoolData(pool *net.IPNet) *PoolData {
|
||||
ipVer := getAddressVersion(pool.IP)
|
||||
ones, bits := pool.Mask.Size()
|
||||
numAddresses := uint64(1 << uint(bits-ones))
|
||||
|
@ -157,10 +138,7 @@ func (a *Allocator) insertBitMask(key SubnetKey, pool *net.IPNet) error {
|
|||
}
|
||||
|
||||
// Generate the new address masks.
|
||||
h, err := bitseq.NewHandle("", nil, "", numAddresses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := bitmap.New(numAddresses)
|
||||
|
||||
// Pre-reserve the network address on IPv4 networks large
|
||||
// enough to have one (i.e., anything bigger than a /31.
|
||||
|
@ -174,26 +152,7 @@ func (a *Allocator) insertBitMask(key SubnetKey, pool *net.IPNet) error {
|
|||
h.Set(numAddresses - 1)
|
||||
}
|
||||
|
||||
a.Lock()
|
||||
a.addresses[key] = h
|
||||
a.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Allocator) retrieveBitmask(k SubnetKey, n *net.IPNet) (*bitseq.Handle, error) {
|
||||
a.Lock()
|
||||
bm, ok := a.addresses[k]
|
||||
a.Unlock()
|
||||
if !ok {
|
||||
logrus.Debugf("Retrieving bitmask (%s, %s)", k, n)
|
||||
if err := a.insertBitMask(k, n); err != nil {
|
||||
return nil, types.InternalErrorf("could not find bitmask for %s", k.String())
|
||||
}
|
||||
a.Lock()
|
||||
bm = a.addresses[k]
|
||||
a.Unlock()
|
||||
}
|
||||
return bm, nil
|
||||
return &PoolData{Pool: pool, addrs: h, children: map[string]struct{}{}}
|
||||
}
|
||||
|
||||
// getPredefineds returns the predefined subnets for the address space.
|
||||
|
@ -219,18 +178,13 @@ func (aSpace *addrSpace) updatePredefinedStartIndex(amt int) {
|
|||
aSpace.predefinedStartIndex = i
|
||||
}
|
||||
|
||||
func (a *Allocator) getPredefinedPool(as string, ipV6 bool) (*net.IPNet, error) {
|
||||
func (aSpace *addrSpace) allocatePredefinedPool(ipV6 bool) (*net.IPNet, SubnetKey, error) {
|
||||
var v ipVersion
|
||||
v = v4
|
||||
if ipV6 {
|
||||
v = v6
|
||||
}
|
||||
|
||||
aSpace, _ := a.getAddrSpace(as)
|
||||
if aSpace == nil {
|
||||
return nil, types.NotImplementedErrorf("no default pool available for non-default address spaces")
|
||||
}
|
||||
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
|
@ -239,24 +193,28 @@ func (a *Allocator) getPredefinedPool(as string, ipV6 bool) (*net.IPNet, error)
|
|||
continue
|
||||
}
|
||||
// Checks whether pool has already been allocated
|
||||
if _, ok := aSpace.subnets[SubnetKey{AddressSpace: as, Subnet: nw.String()}]; ok {
|
||||
if _, ok := aSpace.subnets[nw.String()]; ok {
|
||||
continue
|
||||
}
|
||||
// Shouldn't be necessary, but check prevents IP collisions should
|
||||
// predefined pools overlap for any reason.
|
||||
if !aSpace.contains(as, nw) {
|
||||
if !aSpace.contains(nw) {
|
||||
aSpace.updatePredefinedStartIndex(i + 1)
|
||||
return nw, nil
|
||||
k, err := aSpace.allocateSubnetL(nw, nil)
|
||||
if err != nil {
|
||||
return nil, SubnetKey{}, err
|
||||
}
|
||||
return nw, k, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, types.NotFoundErrorf("could not find an available, non-overlapping IPv%d address pool among the defaults to assign to the network", v)
|
||||
return nil, SubnetKey{}, types.NotFoundErrorf("could not find an available, non-overlapping IPv%d address pool among the defaults to assign to the network", v)
|
||||
}
|
||||
|
||||
// RequestAddress returns an address from the specified pool ID
|
||||
func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
|
||||
logrus.Debugf("RequestAddress(%s, %v, %v)", poolID, prefAddress, opts)
|
||||
k := SubnetKey{}
|
||||
k := PoolID{}
|
||||
if err := k.FromString(poolID); err != nil {
|
||||
return nil, nil, types.BadRequestErrorf("invalid pool id: %s", poolID)
|
||||
}
|
||||
|
@ -265,40 +223,41 @@ func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[s
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return aSpace.requestAddress(k.SubnetKey, prefAddress, opts)
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) requestAddress(k SubnetKey, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
|
||||
aSpace.Lock()
|
||||
p, ok := aSpace.subnets[k]
|
||||
defer aSpace.Unlock()
|
||||
|
||||
p, ok := aSpace.subnets[k.Subnet]
|
||||
if !ok {
|
||||
aSpace.Unlock()
|
||||
return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%s", poolID)
|
||||
return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%+v", k)
|
||||
}
|
||||
|
||||
if prefAddress != nil && !p.Pool.Contains(prefAddress) {
|
||||
aSpace.Unlock()
|
||||
return nil, nil, ipamapi.ErrIPOutOfRange
|
||||
}
|
||||
|
||||
c := p
|
||||
for c.Range != nil {
|
||||
k = c.ParentKey
|
||||
c = aSpace.subnets[k]
|
||||
}
|
||||
aSpace.Unlock()
|
||||
|
||||
bm, err := a.retrieveBitmask(k, c.Pool)
|
||||
if err != nil {
|
||||
return nil, nil, types.InternalErrorf("could not find bitmask for %s on address %v request from pool %s: %v",
|
||||
k.String(), prefAddress, poolID, err)
|
||||
}
|
||||
// In order to request for a serial ip address allocation, callers can pass in the option to request
|
||||
// IP allocation serially or first available IP in the subnet
|
||||
var serial bool
|
||||
if opts != nil {
|
||||
if val, ok := opts[ipamapi.AllocSerialPrefix]; ok {
|
||||
serial = (val == "true")
|
||||
var ipr *AddressRange
|
||||
if k.ChildSubnet != "" {
|
||||
if _, ok := p.children[k.ChildSubnet]; !ok {
|
||||
return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%+v", k)
|
||||
}
|
||||
_, sub, err := net.ParseCIDR(k.ChildSubnet)
|
||||
if err != nil {
|
||||
return nil, nil, types.NotFoundErrorf("cannot find address pool for poolID:%+v: %v", k, err)
|
||||
}
|
||||
ipr, err = getAddressRange(sub, p.Pool)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
ip, err := a.getAddress(p.Pool, bm, prefAddress, p.Range, serial)
|
||||
|
||||
// In order to request for a serial ip address allocation, callers can pass in the option to request
|
||||
// IP allocation serially or first available IP in the subnet
|
||||
serial := opts[ipamapi.AllocSerialPrefix] == "true"
|
||||
ip, err := getAddress(p.Pool, p.addrs, prefAddress, ipr, serial)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -309,7 +268,7 @@ func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[s
|
|||
// ReleaseAddress releases the address from the specified pool ID
|
||||
func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
|
||||
logrus.Debugf("ReleaseAddress(%s, %v)", poolID, address)
|
||||
k := SubnetKey{}
|
||||
k := PoolID{}
|
||||
if err := k.FromString(poolID); err != nil {
|
||||
return types.BadRequestErrorf("invalid pool id: %s", poolID)
|
||||
}
|
||||
|
@ -319,48 +278,44 @@ func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return aSpace.releaseAddress(k.SubnetKey, address)
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) releaseAddress(k SubnetKey, address net.IP) error {
|
||||
aSpace.Lock()
|
||||
p, ok := aSpace.subnets[k]
|
||||
defer aSpace.Unlock()
|
||||
|
||||
p, ok := aSpace.subnets[k.Subnet]
|
||||
if !ok {
|
||||
aSpace.Unlock()
|
||||
return types.NotFoundErrorf("cannot find address pool for poolID:%s", poolID)
|
||||
return types.NotFoundErrorf("cannot find address pool for %+v", k)
|
||||
}
|
||||
if k.ChildSubnet != "" {
|
||||
if _, ok := p.children[k.ChildSubnet]; !ok {
|
||||
return types.NotFoundErrorf("cannot find address pool for poolID:%+v", k)
|
||||
}
|
||||
}
|
||||
|
||||
if address == nil {
|
||||
aSpace.Unlock()
|
||||
return types.BadRequestErrorf("invalid address: nil")
|
||||
}
|
||||
|
||||
if !p.Pool.Contains(address) {
|
||||
aSpace.Unlock()
|
||||
return ipamapi.ErrIPOutOfRange
|
||||
}
|
||||
|
||||
c := p
|
||||
for c.Range != nil {
|
||||
k = c.ParentKey
|
||||
c = aSpace.subnets[k]
|
||||
}
|
||||
aSpace.Unlock()
|
||||
|
||||
mask := p.Pool.Mask
|
||||
|
||||
h, err := types.GetHostPartIP(address, mask)
|
||||
if err != nil {
|
||||
return types.InternalErrorf("failed to release address %s: %v", address.String(), err)
|
||||
return types.InternalErrorf("failed to release address %s: %v", address, err)
|
||||
}
|
||||
|
||||
bm, err := a.retrieveBitmask(k, c.Pool)
|
||||
if err != nil {
|
||||
return types.InternalErrorf("could not find bitmask for %s on address %v release from pool %s: %v",
|
||||
k.String(), address, poolID, err)
|
||||
}
|
||||
defer logrus.Debugf("Released address PoolID:%s, Address:%v Sequence:%s", poolID, address, bm)
|
||||
defer logrus.Debugf("Released address Address:%v Sequence:%s", address, p.addrs)
|
||||
|
||||
return bm.Unset(ipToUint64(h))
|
||||
return p.addrs.Unset(ipToUint64(h))
|
||||
}
|
||||
|
||||
func (a *Allocator) getAddress(nw *net.IPNet, bitmask *bitseq.Handle, prefAddress net.IP, ipr *AddressRange, serial bool) (net.IP, error) {
|
||||
func getAddress(nw *net.IPNet, bitmask *bitmap.Bitmap, prefAddress net.IP, ipr *AddressRange, serial bool) (net.IP, error) {
|
||||
var (
|
||||
ordinal uint64
|
||||
err error
|
||||
|
@ -390,9 +345,9 @@ func (a *Allocator) getAddress(nw *net.IPNet, bitmask *bitseq.Handle, prefAddres
|
|||
case nil:
|
||||
// Convert IP ordinal for this subnet into IP address
|
||||
return generateAddress(ordinal, base), nil
|
||||
case bitseq.ErrBitAllocated:
|
||||
case bitmap.ErrBitAllocated:
|
||||
return nil, ipamapi.ErrIPAlreadyAllocated
|
||||
case bitseq.ErrNoBitAvailable:
|
||||
case bitmap.ErrNoBitAvailable:
|
||||
return nil, ipamapi.ErrNoAvailableIPs
|
||||
default:
|
||||
return nil, err
|
||||
|
@ -401,33 +356,32 @@ func (a *Allocator) getAddress(nw *net.IPNet, bitmask *bitseq.Handle, prefAddres
|
|||
|
||||
// DumpDatabase dumps the internal info
|
||||
func (a *Allocator) DumpDatabase() string {
|
||||
a.Lock()
|
||||
aspaces := map[string]*addrSpace{
|
||||
localAddressSpace: a.local,
|
||||
globalAddressSpace: a.global,
|
||||
}
|
||||
a.Unlock()
|
||||
|
||||
var s string
|
||||
var b strings.Builder
|
||||
for _, as := range []string{localAddressSpace, globalAddressSpace} {
|
||||
aSpace := aspaces[as]
|
||||
s = fmt.Sprintf("\n\n%s Config", as)
|
||||
aSpace.Lock()
|
||||
for k, config := range aSpace.subnets {
|
||||
s += fmt.Sprintf("\n%v: %v", k, config)
|
||||
if config.Range == nil {
|
||||
a.retrieveBitmask(k, config.Pool)
|
||||
}
|
||||
fmt.Fprintf(&b, "\n### %s\n", as)
|
||||
b.WriteString(aspaces[as].DumpDatabase())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) DumpDatabase() string {
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
var b strings.Builder
|
||||
for k, config := range aSpace.subnets {
|
||||
fmt.Fprintf(&b, "%v: %v\n", k, config)
|
||||
fmt.Fprintf(&b, " Bitmap: %v\n", config.addrs)
|
||||
for k := range config.children {
|
||||
fmt.Fprintf(&b, " - Subpool: %v\n", k)
|
||||
}
|
||||
aSpace.Unlock()
|
||||
}
|
||||
|
||||
s = fmt.Sprintf("%s\n\nBitmasks", s)
|
||||
for k, bm := range a.addresses {
|
||||
s += fmt.Sprintf("\n%s: %s", k, bm)
|
||||
}
|
||||
|
||||
return s
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// IsBuiltIn returns true for builtin drivers
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/libnetwork/bitseq"
|
||||
"github.com/docker/docker/libnetwork/bitmap"
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/ipamutils"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
|
@ -48,13 +48,13 @@ func TestGetAddressVersion(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestKeyString(t *testing.T) {
|
||||
k := &SubnetKey{AddressSpace: "default", Subnet: "172.27.0.0/16"}
|
||||
k := &PoolID{AddressSpace: "default", SubnetKey: SubnetKey{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{}
|
||||
k2 := &PoolID{}
|
||||
err := k2.FromString(expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -158,7 +158,7 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
assert.NilError(t, err)
|
||||
|
||||
var k0, k1 SubnetKey
|
||||
var k0, k1 PoolID
|
||||
_, err = a.getAddrSpace(localAddressSpace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -166,7 +166,7 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
|
||||
pid0, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure in adding pool")
|
||||
t.Fatalf("Unexpected failure in adding pool: %v", err)
|
||||
}
|
||||
if err := k0.FromString(pid0); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -177,15 +177,13 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets := aSpace.subnets
|
||||
|
||||
if subnets[k0].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure in adding sub pool")
|
||||
t.Fatalf("Unexpected failure in adding sub pool: %v", err)
|
||||
}
|
||||
if err := k1.FromString(pid1); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -200,14 +198,13 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if subnets[k1].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k1, subnets[k1].RefCount)
|
||||
if got := aSpace.subnets[k1.Subnet].autoRelease; got != false {
|
||||
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)
|
||||
if err == nil {
|
||||
t.Fatal("Expected failure in adding sub pool")
|
||||
t.Fatalf("Expected failure in adding sub pool: %v", err)
|
||||
}
|
||||
|
||||
aSpace, err = a.getAddrSpace(localAddressSpace)
|
||||
|
@ -215,10 +212,8 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
|
||||
if subnets[k0].RefCount != 2 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
|
||||
}
|
||||
|
||||
if err := a.ReleasePool(pid1); err != nil {
|
||||
|
@ -230,20 +225,23 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if subnets[k0].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, ok := aSpace.subnets[k0.Subnet]; ok {
|
||||
t.Error("Pool should have been deleted when released")
|
||||
}
|
||||
|
||||
pid00, _, _, err := a.RequestPool(localAddressSpace, "10.0.0.0/8", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected failure in adding pool")
|
||||
t.Errorf("Unexpected failure in adding pool: %v", err)
|
||||
}
|
||||
if pid00 != pid0 {
|
||||
t.Fatal("main pool should still exist")
|
||||
t.Errorf("main pool should still exist. Got poolID %q, want %q", pid00, pid0)
|
||||
}
|
||||
|
||||
aSpace, err = a.getAddrSpace(localAddressSpace)
|
||||
|
@ -251,13 +249,12 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if subnets[k0].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
|
||||
}
|
||||
|
||||
if err := a.ReleasePool(pid00); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
aSpace, err = a.getAddrSpace(localAddressSpace)
|
||||
|
@ -265,14 +262,13 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if bp, ok := subnets[k0]; ok {
|
||||
t.Fatalf("Base pool %s is still present: %v", k0, bp)
|
||||
if bp, ok := aSpace.subnets[k0.Subnet]; ok {
|
||||
t.Errorf("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")
|
||||
t.Errorf("Unexpected failure in adding pool: %v", err)
|
||||
}
|
||||
|
||||
aSpace, err = a.getAddrSpace(localAddressSpace)
|
||||
|
@ -280,9 +276,8 @@ func TestAddReleasePoolID(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subnets = aSpace.subnets
|
||||
if subnets[k0].RefCount != 1 {
|
||||
t.Fatalf("Unexpected ref count for %s: %d", k0, subnets[k0].RefCount)
|
||||
if got := aSpace.subnets[k0.Subnet].autoRelease; got != false {
|
||||
t.Errorf("Unexpected autoRelease value for %s: %v", k0, got)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,19 +285,16 @@ func TestPredefinedPool(t *testing.T) {
|
|||
a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
|
||||
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)
|
||||
pid2, nw2, _, err := a.RequestPool(localAddressSpace, "", "", nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if types.CompareIPNet(nw, nw2) {
|
||||
t.Fatalf("Unexpected default network returned: %s = %s", nw2, nw)
|
||||
}
|
||||
|
@ -310,6 +302,10 @@ func TestPredefinedPool(t *testing.T) {
|
|||
if err := a.ReleasePool(pid); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := a.ReleasePool(pid2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveSubnet(t *testing.T) {
|
||||
|
@ -958,7 +954,7 @@ func TestRelease(t *testing.T) {
|
|||
for i, inp := range toRelease {
|
||||
ip0 := net.ParseIP(inp.address)
|
||||
a.ReleaseAddress(pid, ip0)
|
||||
bm := a.addresses[SubnetKey{localAddressSpace, subnet, ""}]
|
||||
bm := a.local.subnets[subnet].addrs
|
||||
if bm.Unselected() != 1 {
|
||||
t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected())
|
||||
}
|
||||
|
@ -978,7 +974,6 @@ func assertGetAddress(t *testing.T, subnet string) {
|
|||
var (
|
||||
err error
|
||||
printTime = false
|
||||
a = &Allocator{}
|
||||
)
|
||||
|
||||
_, sub, _ := net.ParseCIDR(subnet)
|
||||
|
@ -986,15 +981,12 @@ func assertGetAddress(t *testing.T, subnet string) {
|
|||
zeroes := bits - ones
|
||||
numAddresses := 1 << uint(zeroes)
|
||||
|
||||
bm, err := bitseq.NewHandle("ipam_test", nil, "default/"+subnet, uint64(numAddresses))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bm := bitmap.New(uint64(numAddresses))
|
||||
|
||||
start := time.Now()
|
||||
run := 0
|
||||
for err != ipamapi.ErrNoAvailableIPs {
|
||||
_, err = a.getAddress(sub, bm, nil, nil, false)
|
||||
_, err = getAddress(sub, bm, nil, nil, false)
|
||||
run++
|
||||
}
|
||||
if printTime {
|
||||
|
@ -1030,6 +1022,9 @@ func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP str
|
|||
start := time.Now()
|
||||
for ; i < numReq; i++ {
|
||||
nw, _, err = a.RequestAddress(pid, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if printTime {
|
||||
fmt.Printf("\nTaken %v, to allocate %d addresses on %s\n", time.Since(start), numReq, subnet)
|
||||
|
|
|
@ -6,29 +6,36 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/libnetwork/bitmap"
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
)
|
||||
|
||||
// SubnetKey is the pointer to the configured pools in each address space
|
||||
type SubnetKey struct {
|
||||
// PoolID is the pointer to the configured pools in each address space
|
||||
type PoolID struct {
|
||||
AddressSpace string
|
||||
Subnet string
|
||||
ChildSubnet string
|
||||
SubnetKey
|
||||
}
|
||||
|
||||
// PoolData contains the configured pool data
|
||||
type PoolData struct {
|
||||
ParentKey SubnetKey
|
||||
Pool *net.IPNet
|
||||
Range *AddressRange `json:",omitempty"`
|
||||
RefCount int
|
||||
Pool *net.IPNet
|
||||
addrs *bitmap.Bitmap
|
||||
children map[string]struct{}
|
||||
|
||||
// Whether to implicitly release the pool once it no longer has any children.
|
||||
autoRelease bool
|
||||
}
|
||||
|
||||
// SubnetKey is the composite key to an address pool within an address space.
|
||||
type SubnetKey struct {
|
||||
Subnet, ChildSubnet string
|
||||
}
|
||||
|
||||
// addrSpace contains the pool configurations for the address space
|
||||
type addrSpace struct {
|
||||
subnets map[SubnetKey]*PoolData
|
||||
alloc *Allocator
|
||||
// Master subnet pools, indexed by the value's stringified PoolData.Pool field.
|
||||
subnets map[string]*PoolData
|
||||
|
||||
// Predefined pool for the address space
|
||||
predefined []*net.IPNet
|
||||
|
@ -50,7 +57,7 @@ func (r *AddressRange) String() string {
|
|||
}
|
||||
|
||||
// String returns the string form of the SubnetKey object
|
||||
func (s *SubnetKey) String() string {
|
||||
func (s *PoolID) String() string {
|
||||
k := fmt.Sprintf("%s/%s", s.AddressSpace, s.Subnet)
|
||||
if s.ChildSubnet != "" {
|
||||
k = fmt.Sprintf("%s/%s", k, s.ChildSubnet)
|
||||
|
@ -59,7 +66,7 @@ func (s *SubnetKey) String() string {
|
|||
}
|
||||
|
||||
// FromString populates the SubnetKey object reading it from string
|
||||
func (s *SubnetKey) FromString(str string) error {
|
||||
func (s *PoolID) FromString(str string) error {
|
||||
if str == "" || !strings.Contains(str, "/") {
|
||||
return types.BadRequestErrorf("invalid string form for subnetkey: %s", str)
|
||||
}
|
||||
|
@ -79,98 +86,94 @@ func (s *SubnetKey) FromString(str string) error {
|
|||
|
||||
// String returns the string form of the PoolData object
|
||||
func (p *PoolData) String() string {
|
||||
return fmt.Sprintf("ParentKey: %s, Pool: %s, Range: %s, RefCount: %d",
|
||||
p.ParentKey.String(), p.Pool.String(), p.Range, p.RefCount)
|
||||
return fmt.Sprintf("Pool: %s, Children: %d",
|
||||
p.Pool.String(), len(p.children))
|
||||
}
|
||||
|
||||
// allocateSubnet adds the subnet k to the address space.
|
||||
func (aSpace *addrSpace) allocateSubnet(k SubnetKey, nw *net.IPNet, ipr *AddressRange, pdf bool) error {
|
||||
func (aSpace *addrSpace) allocateSubnet(nw, sub *net.IPNet) (SubnetKey, error) {
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
// Check if already allocated
|
||||
if _, ok := aSpace.subnets[k]; ok {
|
||||
if pdf {
|
||||
return types.InternalMaskableErrorf("predefined pool %s is already reserved", nw)
|
||||
if pool, ok := aSpace.subnets[nw.String()]; ok {
|
||||
var childExists bool
|
||||
if sub != nil {
|
||||
_, childExists = pool.children[sub.String()]
|
||||
}
|
||||
if sub == nil || childExists {
|
||||
// This means the same pool is already allocated. allocateSubnet is called when there
|
||||
// is request for a pool/subpool. It should ensure there is no overlap with existing pools
|
||||
return SubnetKey{}, ipamapi.ErrPoolOverlap
|
||||
}
|
||||
// This means the same pool is already allocated. allocateSubnet is called when there
|
||||
// is request for a pool/subpool. It should ensure there is no overlap with existing pools
|
||||
return ipamapi.ErrPoolOverlap
|
||||
}
|
||||
|
||||
return aSpace.allocateSubnetL(nw, sub)
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) allocateSubnetL(nw, sub *net.IPNet) (SubnetKey, error) {
|
||||
// If master pool, check for overlap
|
||||
if ipr == nil {
|
||||
if aSpace.contains(k.AddressSpace, nw) {
|
||||
return ipamapi.ErrPoolOverlap
|
||||
if sub == nil {
|
||||
if aSpace.contains(nw) {
|
||||
return SubnetKey{}, ipamapi.ErrPoolOverlap
|
||||
}
|
||||
k := SubnetKey{Subnet: nw.String()}
|
||||
// This is a new master pool, add it along with corresponding bitmask
|
||||
aSpace.subnets[k] = &PoolData{Pool: nw, RefCount: 1}
|
||||
return aSpace.alloc.insertBitMask(k, nw)
|
||||
aSpace.subnets[k.Subnet] = newPoolData(nw)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// This is a new non-master pool (subPool)
|
||||
p := &PoolData{
|
||||
ParentKey: SubnetKey{AddressSpace: k.AddressSpace, Subnet: k.Subnet},
|
||||
Pool: nw,
|
||||
Range: ipr,
|
||||
RefCount: 1,
|
||||
|
||||
_, err := getAddressRange(sub, nw)
|
||||
if err != nil {
|
||||
return SubnetKey{}, err
|
||||
}
|
||||
aSpace.subnets[k] = p
|
||||
|
||||
k := SubnetKey{Subnet: nw.String(), ChildSubnet: sub.String()}
|
||||
|
||||
// Look for parent pool
|
||||
pp, ok := aSpace.subnets[p.ParentKey]
|
||||
if ok {
|
||||
aSpace.incRefCount(pp, 1)
|
||||
return nil
|
||||
pp, ok := aSpace.subnets[k.Subnet]
|
||||
if !ok {
|
||||
// Parent pool does not exist, add it along with corresponding bitmask
|
||||
pp = newPoolData(nw)
|
||||
pp.autoRelease = true
|
||||
aSpace.subnets[k.Subnet] = pp
|
||||
}
|
||||
|
||||
// Parent pool does not exist, add it along with corresponding bitmask
|
||||
aSpace.subnets[p.ParentKey] = &PoolData{Pool: nw, RefCount: 1}
|
||||
return aSpace.alloc.insertBitMask(p.ParentKey, nw)
|
||||
pp.children[k.ChildSubnet] = struct{}{}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) releaseSubnet(k SubnetKey) error {
|
||||
aSpace.Lock()
|
||||
defer aSpace.Unlock()
|
||||
|
||||
p, ok := aSpace.subnets[k]
|
||||
p, ok := aSpace.subnets[k.Subnet]
|
||||
if !ok {
|
||||
return ipamapi.ErrBadPool
|
||||
}
|
||||
|
||||
aSpace.incRefCount(p, -1)
|
||||
|
||||
c := p
|
||||
for ok {
|
||||
if c.RefCount == 0 {
|
||||
delete(aSpace.subnets, k)
|
||||
if c.Range == nil {
|
||||
return nil
|
||||
}
|
||||
if k.ChildSubnet != "" {
|
||||
if _, ok := p.children[k.ChildSubnet]; !ok {
|
||||
return ipamapi.ErrBadPool
|
||||
}
|
||||
k = c.ParentKey
|
||||
c, ok = aSpace.subnets[k]
|
||||
delete(p.children, k.ChildSubnet)
|
||||
} else {
|
||||
p.autoRelease = true
|
||||
}
|
||||
|
||||
if len(p.children) == 0 && p.autoRelease {
|
||||
delete(aSpace.subnets, k.Subnet)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (aSpace *addrSpace) incRefCount(p *PoolData, delta int) {
|
||||
c := p
|
||||
ok := true
|
||||
for ok {
|
||||
c.RefCount += delta
|
||||
c, ok = aSpace.subnets[c.ParentKey]
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether the passed subnet is a superset or subset of any of the subset in this config db
|
||||
func (aSpace *addrSpace) contains(space string, nw *net.IPNet) bool {
|
||||
for k, v := range aSpace.subnets {
|
||||
if space == k.AddressSpace && k.ChildSubnet == "" {
|
||||
if nw.Contains(v.Pool.IP) || v.Pool.Contains(nw.IP) {
|
||||
return true
|
||||
}
|
||||
// contains checks whether nw is a superset or subset of any of the existing subnets in this address space.
|
||||
func (aSpace *addrSpace) contains(nw *net.IPNet) bool {
|
||||
for _, v := range aSpace.subnets {
|
||||
if nw.Contains(v.Pool.IP) || v.Pool.Contains(nw.IP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
)
|
||||
|
||||
|
@ -15,11 +14,7 @@ const (
|
|||
v6 = 6
|
||||
)
|
||||
|
||||
func getAddressRange(pool string, masterNw *net.IPNet) (*AddressRange, error) {
|
||||
ip, nw, err := net.ParseCIDR(pool)
|
||||
if err != nil {
|
||||
return nil, ipamapi.ErrInvalidSubPool
|
||||
}
|
||||
func getAddressRange(nw, masterNw *net.IPNet) (*AddressRange, error) {
|
||||
lIP, e := types.GetHostPartIP(nw.IP, masterNw.Mask)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's lowest ip address: %v", e)
|
||||
|
@ -32,7 +27,6 @@ func getAddressRange(pool string, masterNw *net.IPNet) (*AddressRange, error) {
|
|||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's highest ip address: %v", e)
|
||||
}
|
||||
nw.IP = ip
|
||||
return &AddressRange{nw, ipToUint64(types.GetMinimalIP(lIP)), ipToUint64(types.GetMinimalIP(hIP))}, nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue