Skip to content

Commit

Permalink
Added AcquireSpecificChildPrefix (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidefalcone1 authored Mar 22, 2021
1 parent 2258e54 commit 5a9da50
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 16 deletions.
54 changes: 54 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ func TestIntegration(t *testing.T) {
require.Equal(t, "", publicInternet.ParentCidr)
_, err = ipam.AcquireChildPrefix(publicInternet.Cidr, 29)
require.EqualError(t, err, "prefix 1.2.3.0/27 has ips, acquire child prefix not possible")
_, err = ipam.AcquireSpecificChildPrefix(publicInternet.Cidr, "1.2.3.0/29")
require.EqualError(t, err, "prefix 1.2.3.0/27 has ips, acquire child prefix not possible")
ip, err := ipam.AcquireIP(publicInternet.Cidr)
require.NoError(t, err)
require.NotNil(t, ip)
Expand Down Expand Up @@ -86,6 +88,22 @@ func TestIntegration(t *testing.T) {
require.NotNil(t, tenantSuper)
require.Equal(t, 18, int(tenantSuper.Usage().AcquiredPrefixes))

cp, err = ipam.AcquireSpecificChildPrefix("10.128.0.0/14", "10.128.4.0/22")
require.NoError(t, err)
require.NotNil(t, cp)
require.Equal(t, "10.128.4.0/22", cp.String())
require.Equal(t, "10.128.0.0/14", cp.ParentCidr)

// reread
tenantSuper = ipam.PrefixFrom("10.128.0.0/14")
require.NotNil(t, tenantSuper)
require.Equal(t, 19, int(tenantSuper.Usage().AcquiredPrefixes))
err = ipam.ReleaseChildPrefix(cp)
require.NoError(t, err)
// reread
tenantSuper = ipam.PrefixFrom("10.128.0.0/14")
require.NotNil(t, tenantSuper)

_, err = ipam.AcquireIP("10.128.0.0/14")
require.EqualError(t, err, "prefix 10.128.0.0/14 has childprefixes, acquire ip not possible")

Expand Down Expand Up @@ -153,6 +171,23 @@ func TestIntegrationP(t *testing.T) {
require.NotNil(t, tenantSuper1)
require.Equal(t, 36, int(tenantSuper1.Usage().AcquiredPrefixes))

cp, err = ipam.AcquireSpecificChildPrefix("10.64.0.0/14", "10.64.0.0/22")
require.NoError(t, err)
require.NotNil(t, cp)
require.Equal(t, "10.64.0.0/22", cp.String())
require.Equal(t, "10.64.0.0/14", cp.ParentCidr)

// reread
tenantSuper1 = ipam.PrefixFrom("10.64.0.0/14")
require.NotNil(t, tenantSuper1)
require.Equal(t, 37, int(tenantSuper1.Usage().AcquiredPrefixes))
err = ipam.ReleaseChildPrefix(cp)
require.NoError(t, err)
// reread
tenantSuper1 = ipam.PrefixFrom("10.64.0.0/14")
require.NotNil(t, tenantSuper1)
require.Equal(t, 36, int(tenantSuper1.Usage().AcquiredPrefixes))

_, err = ipam.AcquireIP("10.64.0.0/14")
require.EqualError(t, err, "prefix 10.64.0.0/14 has childprefixes, acquire ip not possible")

Expand Down Expand Up @@ -197,6 +232,23 @@ func TestIntegrationP(t *testing.T) {
require.NotNil(t, tenantSuper2)
require.Equal(t, 28, int(tenantSuper2.Usage().AcquiredPrefixes))

cp, err = ipam.AcquireSpecificChildPrefix("10.76.0.0/14", "10.76.0.0/22")
require.NoError(t, err)
require.NotNil(t, cp)
require.Equal(t, "10.76.0.0/22", cp.String())
require.Equal(t, "10.76.0.0/14", cp.ParentCidr)

// reread
tenantSuper2 = ipam.PrefixFrom("10.76.0.0/14")
require.NotNil(t, tenantSuper2)
require.Equal(t, 29, int(tenantSuper2.Usage().AcquiredPrefixes))
err = ipam.ReleaseChildPrefix(cp)
require.NoError(t, err)
// reread
tenantSuper2 = ipam.PrefixFrom("10.76.0.0/14")
require.NotNil(t, tenantSuper2)
require.Equal(t, 28, int(tenantSuper2.Usage().AcquiredPrefixes))

_, err = ipam.AcquireIP("10.76.0.0/14")
require.EqualError(t, err, "prefix 10.76.0.0/14 has childprefixes, acquire ip not possible")

Expand Down Expand Up @@ -224,6 +276,8 @@ func TestIntegrationP(t *testing.T) {
require.Equal(t, "", publicInternet.ParentCidr)
_, err = ipam.AcquireChildPrefix(publicInternet.Cidr, 29)
require.EqualError(t, err, "prefix 1.2.3.0/25 has ips, acquire child prefix not possible")
_, err = ipam.AcquireSpecificChildPrefix(publicInternet.Cidr, "1.2.3.0/29")
require.EqualError(t, err, "prefix 1.2.3.0/25 has ips, acquire child prefix not possible")
_, err = ipam.AcquireIP(publicInternet.Cidr)
require.EqualError(t, err, "NoIPAvailableError: no more ips in prefix: 1.2.3.0/25 left, length of prefix.ips: 128")

Expand Down
2 changes: 2 additions & 0 deletions ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type Ipamer interface {
DeletePrefix(cidr string) (*Prefix, error)
// AcquireChildPrefix will return a Prefix with a smaller length from the given Prefix.
AcquireChildPrefix(parentCidr string, length uint8) (*Prefix, error)
// AcquireSpecificChildPrefix will return a Prefix with a smaller length from the given Prefix.
AcquireSpecificChildPrefix(parentCidr, childCidr string) (*Prefix, error)
// ReleaseChildPrefix will mark this child Prefix as available again.
ReleaseChildPrefix(child *Prefix) error
// PrefixFrom will return a known Prefix.
Expand Down
61 changes: 45 additions & 16 deletions prefix.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,24 @@ func (i *ipamer) AcquireChildPrefix(parentCidr string, length uint8) (*Prefix, e
var prefix *Prefix
return prefix, retryOnOptimisticLock(func() error {
var err error
prefix, err = i.acquireChildPrefixInternal(parentCidr, length)
prefix, err = i.acquireChildPrefixInternal(parentCidr, "", length)
return err
})
}

func (i *ipamer) AcquireSpecificChildPrefix(parentCidr, childCidr string) (*Prefix, error) {
var prefix *Prefix
return prefix, retryOnOptimisticLock(func() error {
var err error
prefix, err = i.acquireChildPrefixInternal(parentCidr, childCidr, 0)
return err
})
}

// acquireChildPrefixInternal will return a Prefix with a smaller length from the given Prefix.
func (i *ipamer) acquireChildPrefixInternal(parentCidr string, length uint8) (*Prefix, error) {
func (i *ipamer) acquireChildPrefixInternal(parentCidr, childCidr string, length uint8) (*Prefix, error) {
specificChildRequest := childCidr != ""
var childprefix netaddr.IPPrefix
parent := i.PrefixFrom(parentCidr)
if parent == nil {
return nil, fmt.Errorf("unable to find prefix for cidr:%s", parentCidr)
Expand All @@ -197,6 +208,13 @@ func (i *ipamer) acquireChildPrefixInternal(parentCidr string, length uint8) (*P
if err != nil {
return nil, err
}
if specificChildRequest {
childprefix, err = netaddr.ParseIPPrefix(childCidr)
if err != nil {
return nil, err
}
length = childprefix.Bits
}
if ipprefix.Bits >= length {
return nil, fmt.Errorf("given length:%d must be greater than prefix length:%d", length, ipprefix.Bits)
}
Expand All @@ -217,23 +235,34 @@ func (i *ipamer) acquireChildPrefixInternal(parentCidr string, length uint8) (*P
ipset.RemovePrefix(cpipprefix)
}

cp, _, ok := ipset.IPSet().RemoveFreePrefix(length)
if !ok {
pfxs := ipset.IPSet().Prefixes()
if len(pfxs) == 0 {
return nil, fmt.Errorf("no prefix found in %s with length:%d", parentCidr, length)
}
var cp netaddr.IPPrefix

if !specificChildRequest {
var ok bool
cp, _, ok = ipset.IPSet().RemoveFreePrefix(length)
if !ok {
pfxs := ipset.IPSet().Prefixes()
if len(pfxs) == 0 {
return nil, fmt.Errorf("no prefix found in %s with length:%d", parentCidr, length)
}

var availablePrefixes []string
for _, p := range pfxs {
availablePrefixes = append(availablePrefixes, p.String())
var availablePrefixes []string
for _, p := range pfxs {
availablePrefixes = append(availablePrefixes, p.String())
}
adj := "are"
if len(availablePrefixes) == 1 {
adj = "is"
}

return nil, fmt.Errorf("no prefix found in %s with length:%d, but %s %s available", parentCidr, length, strings.Join(availablePrefixes, ","), adj)
}
adj := "are"
if len(availablePrefixes) == 1 {
adj = "is"
} else {
if ok := ipset.IPSet().ContainsPrefix(childprefix); !ok {
// Parent prefix does not contain specific child prefix
return nil, fmt.Errorf("specific prefix %s is not available in prefix %s", childCidr, parentCidr)
}

return nil, fmt.Errorf("no prefix found in %s with length:%d, but %s %s available", parentCidr, length, strings.Join(availablePrefixes, ","), adj)
cp = childprefix
}

child := &Prefix{
Expand Down
129 changes: 129 additions & 0 deletions prefix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,135 @@ func TestIpamer_AcquireChildPrefixIPv6(t *testing.T) {
})
}

func TestIpamer_AcquireSpecificChildPrefixIPv4(t *testing.T) {
testWithBackends(t, func(t *testing.T, ipam *ipamer) {
prefix, err := ipam.NewPrefix("192.168.0.0/20")
require.Nil(t, err)
s, _ := prefix.availablePrefixes()
require.Equal(t, uint64(1024), s)
require.Equal(t, prefix.acquiredPrefixes(), uint64(0))

// Same length
cp, err := ipam.AcquireSpecificChildPrefix(prefix.Cidr, "192.168.0.0/20")
require.NotNil(t, err)
require.Equal(t, "given length:20 must be greater than prefix length:20", err.Error())
require.Nil(t, cp)

// working length
cp, err = ipam.AcquireSpecificChildPrefix(prefix.Cidr, "192.168.0.0/21")
require.Nil(t, err)
require.NotNil(t, cp)
require.Equal(t, cp.Cidr, "192.168.0.0/21")
require.Equal(t, prefix.Cidr, cp.ParentCidr)

// specific prefix not available
cp, err = ipam.AcquireSpecificChildPrefix(prefix.Cidr, "192.168.8.0/21")
require.Nil(t, err)
require.NotNil(t, cp)
cp, err = ipam.AcquireSpecificChildPrefix(prefix.Cidr, "192.168.8.0/21")
require.NotNil(t, err)
require.Equal(t, "specific prefix 192.168.8.0/21 is not available in prefix 192.168.0.0/20", err.Error())
require.Nil(t, cp)

// Prefix has ips
p2, err := ipam.NewPrefix("10.0.0.0/24")
require.Nil(t, err)
s, _ = p2.availablePrefixes()
require.Equal(t, uint64(64), s)
require.Equal(t, p2.acquiredPrefixes(), uint64(0))
ip, err := ipam.AcquireIP(p2.Cidr)
require.Nil(t, err)
require.NotNil(t, ip)
cp2, err := ipam.AcquireSpecificChildPrefix(p2.Cidr, "10.0.0.0/25")
require.NotNil(t, err)
require.Equal(t, "prefix 10.0.0.0/24 has ips, acquire child prefix not possible", err.Error())
require.Nil(t, cp2)

// Prefix has Childs, AcquireIP wont work
p3, err := ipam.NewPrefix("172.17.0.0/24")
require.Nil(t, err)
s, _ = p3.availablePrefixes()
require.Equal(t, uint64(64), s)
require.Equal(t, p3.acquiredPrefixes(), uint64(0))
cp3, err := ipam.AcquireSpecificChildPrefix(p3.Cidr, "172.17.0.0/25")
require.Nil(t, err)
require.NotNil(t, cp3)
p3 = ipam.PrefixFrom(p3.Cidr)
ip, err = ipam.AcquireIP(p3.Cidr)
require.NotNil(t, err)
require.Equal(t, "prefix 172.17.0.0/24 has childprefixes, acquire ip not possible", err.Error())
require.Nil(t, ip)
})
}

func TestIpamer_AcquireSpecificChildPrefixIPv6(t *testing.T) {

testWithBackends(t, func(t *testing.T, ipam *ipamer) {
prefix, err := ipam.NewPrefix("2001:0db8:85a3::/116")
require.Nil(t, err)
s, _ := prefix.availablePrefixes()
require.Equal(t, uint64(1024), s)
require.Equal(t, prefix.acquiredPrefixes(), uint64(0))

// Same length
cp, err := ipam.AcquireSpecificChildPrefix(prefix.Cidr, "2001:0db8:85a3::/116")
require.NotNil(t, err)
require.Equal(t, "given length:116 must be greater than prefix length:116", err.Error())
require.Nil(t, cp)

// working length
cp, err = ipam.AcquireSpecificChildPrefix(prefix.Cidr, "2001:0db8:85a3::/117")
require.Nil(t, err)
require.NotNil(t, cp)
require.Equal(t, "2001:db8:85a3::/117", cp.Cidr)
require.Equal(t, prefix.Cidr, cp.ParentCidr)

// specific prefix not available
cp, err = ipam.AcquireSpecificChildPrefix(prefix.Cidr, "2001:0db8:85a3::0800/117")
require.Nil(t, err)
require.NotNil(t, cp)
require.Equal(t, cp.Cidr, "2001:db8:85a3::800/117")
cp, err = ipam.AcquireSpecificChildPrefix(prefix.Cidr, "2001:0db8:85a3::0800/117")
require.NotNil(t, err)
require.Equal(t, "specific prefix 2001:0db8:85a3::0800/117 is not available in prefix 2001:db8:85a3::/116", err.Error())
require.Nil(t, cp)

// Prefix has ips
p2, err := ipam.NewPrefix("2001:0db8:95a3::/120")
require.Nil(t, err)
s, _ = p2.availablePrefixes()
require.Equal(t, uint64(64), s)
require.Equal(t, p2.acquiredPrefixes(), uint64(0))
ip, err := ipam.AcquireIP(p2.Cidr)
require.Nil(t, err)
require.NotNil(t, ip)
cp2, err := ipam.AcquireSpecificChildPrefix(p2.Cidr, "2001:0db8:95a3::/121")
require.NotNil(t, err)
require.Equal(t, "prefix 2001:db8:95a3::/120 has ips, acquire child prefix not possible", err.Error())
require.Nil(t, cp2)

// Prefix has Childs, AcquireIP wont work
p3, err := ipam.NewPrefix("2001:0db8:75a3::/120")
require.Nil(t, err)
s, _ = p3.availablePrefixes()
require.Equal(t, uint64(64), s)
require.Equal(t, p3.acquiredPrefixes(), uint64(0))
cp3, err := ipam.AcquireSpecificChildPrefix(p3.Cidr, "2001:0db8:75a3::/121")
require.Nil(t, err)
require.NotNil(t, cp3)
p3 = ipam.PrefixFrom(p3.Cidr)
ip, err = ipam.AcquireIP(p3.Cidr)
require.NotNil(t, err)
require.Equal(t, "prefix 2001:db8:75a3::/120 has childprefixes, acquire ip not possible", err.Error())
require.Nil(t, ip)

// Release Parent Prefix must not work
err = ipam.ReleaseChildPrefix(p3)
require.NotNil(t, err)
require.Equal(t, "prefix 2001:db8:75a3::/120 is no child prefix", err.Error())
})
}

func TestIpamer_AcquireChildPrefixNoDuplicatesUntilFullIPv6(t *testing.T) {
testWithBackends(t, func(t *testing.T, ipam *ipamer) {
prefix, err := ipam.NewPrefix("2001:0db8:85a3::/112")
Expand Down

0 comments on commit 5a9da50

Please sign in to comment.