From 5a9da5064b27c6f6c0cc217cb7584152008fd856 Mon Sep 17 00:00:00 2001 From: Davide Falcone Date: Mon, 22 Mar 2021 09:02:03 +0100 Subject: [PATCH] Added AcquireSpecificChildPrefix (#54) --- integration_test.go | 54 +++++++++++++++++++ ipam.go | 2 + prefix.go | 61 +++++++++++++++------ prefix_test.go | 129 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+), 16 deletions(-) diff --git a/integration_test.go b/integration_test.go index 0f85e40..33c48bd 100644 --- a/integration_test.go +++ b/integration_test.go @@ -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) @@ -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") @@ -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") @@ -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") @@ -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") diff --git a/ipam.go b/ipam.go index 343b914..35261fa 100644 --- a/ipam.go +++ b/ipam.go @@ -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. diff --git a/prefix.go b/prefix.go index 2fe92e0..b0e317b 100644 --- a/prefix.go +++ b/prefix.go @@ -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) @@ -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) } @@ -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{ diff --git a/prefix_test.go b/prefix_test.go index 93c9e69..cfc800c 100644 --- a/prefix_test.go +++ b/prefix_test.go @@ -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")