ipam/host-local: support sets of disjoint ranges
In real-world address allocations, disjoint address ranges are common. Therefore, the host-local allocator should support them. This change still allows for multiple IPs in a single configuration, but also allows for a "set of subnets." Fixes: #45
This commit is contained in:
parent
20bc33abc5
commit
27d027a6d3
@ -8,24 +8,40 @@ it can include a DNS configuration from a `resolv.conf` file on the host.
|
|||||||
host-local IPAM plugin allocates ip addresses out of a set of address ranges.
|
host-local IPAM plugin allocates ip addresses out of a set of address ranges.
|
||||||
It stores the state locally on the host filesystem, therefore ensuring uniqueness of IP addresses on a single host.
|
It stores the state locally on the host filesystem, therefore ensuring uniqueness of IP addresses on a single host.
|
||||||
|
|
||||||
|
The allocator can allocate multiple ranges, and supports sets of multiple (disjoint)
|
||||||
|
subnets. The allocation strategy is loosely round-robin within each range set.
|
||||||
|
|
||||||
## Example configurations
|
## Example configurations
|
||||||
|
|
||||||
|
Note that the key `ranges` is a list of range sets. That is to say, the length
|
||||||
|
of the top-level array is the number of addresses returned. The second-level
|
||||||
|
array is a set of subnets to use as a pool of possible addresses.
|
||||||
|
|
||||||
|
This example configuration returns 2 IP addresses.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
|
[
|
||||||
{
|
{
|
||||||
"subnet": "10.10.0.0/16",
|
"subnet": "10.10.0.0/16",
|
||||||
"rangeStart": "10.10.1.20",
|
"rangeStart": "10.10.1.20",
|
||||||
"rangeEnd": "10.10.3.50",
|
"rangeEnd": "10.10.3.50",
|
||||||
"gateway": "10.10.0.254"
|
"gateway": "10.10.0.254"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"subnet": "172.16.5.0/24"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
{
|
{
|
||||||
"subnet": "3ffe:ffff:0:01ff::/64",
|
"subnet": "3ffe:ffff:0:01ff::/64",
|
||||||
"rangeStart": "3ffe:ffff:0:01ff::0010",
|
"rangeStart": "3ffe:ffff:0:01ff::0010",
|
||||||
"rangeEnd": "3ffe:ffff:0:01ff::0020"
|
"rangeEnd": "3ffe:ffff:0:01ff::0020"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{ "dst": "0.0.0.0/0" },
|
{ "dst": "0.0.0.0/0" },
|
||||||
@ -58,7 +74,7 @@ deprecated but still supported.
|
|||||||
We can test it out on the command-line:
|
We can test it out on the command-line:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ echo '{ "cniVersion": "0.3.1", "name": "examplenet", "ipam": { "type": "host-local", "ranges": [ {"subnet": "203.0.113.0/24"}, {"subnet": "2001:db8:1::/64"}], "dataDir": "/tmp/cni-example" } }' | CNI_COMMAND=ADD CNI_CONTAINERID=example CNI_NETNS=/dev/null CNI_IFNAME=dummy0 CNI_PATH=. ./host-local
|
$ echo '{ "cniVersion": "0.3.1", "name": "examplenet", "ipam": { "type": "host-local", "ranges": [ [{"subnet": "203.0.113.0/24"}], [{"subnet": "2001:db8:1::/64"}]], "dataDir": "/tmp/cni-example" } }' | CNI_COMMAND=ADD CNI_CONTAINERID=example CNI_NETNS=/dev/null CNI_IFNAME=dummy0 CNI_PATH=. ./host-local
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -86,7 +102,7 @@ $ echo '{ "cniVersion": "0.3.1", "name": "examplenet", "ipam": { "type": "host-l
|
|||||||
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
|
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
|
||||||
* `resolvConf` (string, optional): Path to a `resolv.conf` on the host to parse and return as the DNS configuration
|
* `resolvConf` (string, optional): Path to a `resolv.conf` on the host to parse and return as the DNS configuration
|
||||||
* `dataDir` (string, optional): Path to a directory to use for maintaining state, e.g. which IPs have been allocated to which containers
|
* `dataDir` (string, optional): Path to a directory to use for maintaining state, e.g. which IPs have been allocated to which containers
|
||||||
* `ranges`, (array, required, nonempty) an array of range objects:
|
* `ranges`, (array, required, nonempty) an array of arrays of range objects:
|
||||||
* `subnet` (string, required): CIDR block to allocate out of.
|
* `subnet` (string, required): CIDR block to allocate out of.
|
||||||
* `rangeStart` (string, optional): IP inside of "subnet" from which to start allocating addresses. Defaults to ".2" IP inside of the "subnet" block.
|
* `rangeStart` (string, optional): IP inside of "subnet" from which to start allocating addresses. Defaults to ".2" IP inside of the "subnet" block.
|
||||||
* `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block for ipv4, ".255" for IPv6
|
* `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block for ipv4, ".255" for IPv6
|
||||||
|
@ -15,11 +15,11 @@
|
|||||||
package allocator
|
package allocator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types/current"
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
"github.com/containernetworking/plugins/pkg/ip"
|
"github.com/containernetworking/plugins/pkg/ip"
|
||||||
@ -27,29 +27,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type IPAllocator struct {
|
type IPAllocator struct {
|
||||||
netName string
|
rangeset *RangeSet
|
||||||
ipRange Range
|
|
||||||
store backend.Store
|
store backend.Store
|
||||||
rangeID string // Used for tracking last reserved ip
|
rangeID string // Used for tracking last reserved ip
|
||||||
}
|
}
|
||||||
|
|
||||||
type RangeIter struct {
|
func NewIPAllocator(s *RangeSet, store backend.Store, id int) *IPAllocator {
|
||||||
low net.IP
|
|
||||||
high net.IP
|
|
||||||
cur net.IP
|
|
||||||
start net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIPAllocator(netName string, r Range, store backend.Store) *IPAllocator {
|
|
||||||
// The range name (last allocated ip suffix) is just the base64
|
|
||||||
// encoding of the bytes of the first IP
|
|
||||||
rangeID := base64.URLEncoding.EncodeToString(r.RangeStart)
|
|
||||||
|
|
||||||
return &IPAllocator{
|
return &IPAllocator{
|
||||||
netName: netName,
|
rangeset: s,
|
||||||
ipRange: r,
|
|
||||||
store: store,
|
store: store,
|
||||||
rangeID: rangeID,
|
rangeID: strconv.Itoa(id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,27 +45,32 @@ func (a *IPAllocator) Get(id string, requestedIP net.IP) (*current.IPConfig, err
|
|||||||
a.store.Lock()
|
a.store.Lock()
|
||||||
defer a.store.Unlock()
|
defer a.store.Unlock()
|
||||||
|
|
||||||
gw := a.ipRange.Gateway
|
var reservedIP *net.IPNet
|
||||||
|
var gw net.IP
|
||||||
var reservedIP net.IP
|
|
||||||
|
|
||||||
if requestedIP != nil {
|
if requestedIP != nil {
|
||||||
if gw != nil && gw.Equal(requestedIP) {
|
if err := canonicalizeIP(&requestedIP); err != nil {
|
||||||
return nil, fmt.Errorf("requested IP must differ from gateway IP")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.ipRange.IPInRange(requestedIP); err != nil {
|
r, err := a.rangeset.RangeFor(requestedIP)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if requestedIP.Equal(r.Gateway) {
|
||||||
|
return nil, fmt.Errorf("requested ip %s is subnet's gateway", requestedIP.String())
|
||||||
|
}
|
||||||
|
|
||||||
reserved, err := a.store.Reserve(id, requestedIP, a.rangeID)
|
reserved, err := a.store.Reserve(id, requestedIP, a.rangeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !reserved {
|
if !reserved {
|
||||||
return nil, fmt.Errorf("requested IP address %q is not available in network: %s %s", requestedIP, a.netName, (*net.IPNet)(&a.ipRange.Subnet).String())
|
return nil, fmt.Errorf("requested IP address %s is not available in range set %s", requestedIP, a.rangeset.String())
|
||||||
}
|
}
|
||||||
reservedIP = requestedIP
|
reservedIP = &net.IPNet{IP: requestedIP, Mask: r.Subnet.Mask}
|
||||||
|
gw = r.Gateway
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
iter, err := a.GetIter()
|
iter, err := a.GetIter()
|
||||||
@ -86,39 +78,33 @@ func (a *IPAllocator) Get(id string, requestedIP net.IP) (*current.IPConfig, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
cur := iter.Next()
|
reservedIP, gw = iter.Next()
|
||||||
if cur == nil {
|
if reservedIP == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't allocate gateway IP
|
reserved, err := a.store.Reserve(id, reservedIP.IP, a.rangeID)
|
||||||
if gw != nil && cur.Equal(gw) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
reserved, err := a.store.Reserve(id, cur, a.rangeID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if reserved {
|
if reserved {
|
||||||
reservedIP = cur
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if reservedIP == nil {
|
if reservedIP == nil {
|
||||||
return nil, fmt.Errorf("no IP addresses available in network: %s %s", a.netName, (*net.IPNet)(&a.ipRange.Subnet).String())
|
return nil, fmt.Errorf("no IP addresses available in range set: %s", a.rangeset.String())
|
||||||
}
|
}
|
||||||
version := "4"
|
version := "4"
|
||||||
if reservedIP.To4() == nil {
|
if reservedIP.IP.To4() == nil {
|
||||||
version = "6"
|
version = "6"
|
||||||
}
|
}
|
||||||
|
|
||||||
return ¤t.IPConfig{
|
return ¤t.IPConfig{
|
||||||
Version: version,
|
Version: version,
|
||||||
Address: net.IPNet{IP: reservedIP, Mask: a.ipRange.Subnet.Mask},
|
Address: *reservedIP,
|
||||||
Gateway: gw,
|
Gateway: gw,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -131,15 +117,28 @@ func (a *IPAllocator) Release(id string) error {
|
|||||||
return a.store.ReleaseByID(id)
|
return a.store.ReleaseByID(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RangeIter struct {
|
||||||
|
rangeset *RangeSet
|
||||||
|
|
||||||
|
// The current range id
|
||||||
|
rangeIdx int
|
||||||
|
|
||||||
|
// Our current position
|
||||||
|
cur net.IP
|
||||||
|
|
||||||
|
// The IP and range index where we started iterating; if we hit this again, we're done.
|
||||||
|
startIP net.IP
|
||||||
|
startRange int
|
||||||
|
}
|
||||||
|
|
||||||
// GetIter encapsulates the strategy for this allocator.
|
// GetIter encapsulates the strategy for this allocator.
|
||||||
// We use a round-robin strategy, attempting to evenly use the whole subnet.
|
// We use a round-robin strategy, attempting to evenly use the whole set.
|
||||||
// More specifically, a crash-looping container will not see the same IP until
|
// More specifically, a crash-looping container will not see the same IP until
|
||||||
// the entire range has been run through.
|
// the entire range has been run through.
|
||||||
// We may wish to consider avoiding recently-released IPs in the future.
|
// We may wish to consider avoiding recently-released IPs in the future.
|
||||||
func (a *IPAllocator) GetIter() (*RangeIter, error) {
|
func (a *IPAllocator) GetIter() (*RangeIter, error) {
|
||||||
i := RangeIter{
|
iter := RangeIter{
|
||||||
low: a.ipRange.RangeStart,
|
rangeset: a.rangeset,
|
||||||
high: a.ipRange.RangeEnd,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Round-robin by trying to allocate from the last reserved IP + 1
|
// Round-robin by trying to allocate from the last reserved IP + 1
|
||||||
@ -151,39 +150,68 @@ func (a *IPAllocator) GetIter() (*RangeIter, error) {
|
|||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
log.Printf("Error retrieving last reserved ip: %v", err)
|
log.Printf("Error retrieving last reserved ip: %v", err)
|
||||||
} else if lastReservedIP != nil {
|
} else if lastReservedIP != nil {
|
||||||
startFromLastReservedIP = a.ipRange.IPInRange(lastReservedIP) == nil
|
startFromLastReservedIP = a.rangeset.Contains(lastReservedIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the range in the set with this IP
|
||||||
if startFromLastReservedIP {
|
if startFromLastReservedIP {
|
||||||
if i.high.Equal(lastReservedIP) {
|
for i, r := range *a.rangeset {
|
||||||
i.start = i.low
|
if r.Contains(lastReservedIP) {
|
||||||
} else {
|
iter.rangeIdx = i
|
||||||
i.start = ip.NextIP(lastReservedIP)
|
iter.startRange = i
|
||||||
|
|
||||||
|
// We advance the cursor on every Next(), so the first call
|
||||||
|
// to next() will return lastReservedIP + 1
|
||||||
|
iter.cur = lastReservedIP
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
i.start = a.ipRange.RangeStart
|
iter.rangeIdx = 0
|
||||||
|
iter.startRange = 0
|
||||||
|
iter.startIP = (*a.rangeset)[0].RangeStart
|
||||||
}
|
}
|
||||||
return &i, nil
|
return &iter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next IP in the iterator, or nil if end is reached
|
// Next returns the next IP, its mask, and its gateway. Returns nil
|
||||||
func (i *RangeIter) Next() net.IP {
|
// if the iterator has been exhausted
|
||||||
// If we're at the beginning, time to start
|
func (i *RangeIter) Next() (*net.IPNet, net.IP) {
|
||||||
|
r := (*i.rangeset)[i.rangeIdx]
|
||||||
|
|
||||||
|
// If this is the first time iterating and we're not starting in the middle
|
||||||
|
// of the range, then start at rangeStart, which is inclusive
|
||||||
if i.cur == nil {
|
if i.cur == nil {
|
||||||
i.cur = i.start
|
i.cur = r.RangeStart
|
||||||
return i.cur
|
i.startIP = i.cur
|
||||||
|
if i.cur.Equal(r.Gateway) {
|
||||||
|
return i.Next()
|
||||||
}
|
}
|
||||||
// we returned .high last time, since we're inclusive
|
return &net.IPNet{IP: i.cur, Mask: r.Subnet.Mask}, r.Gateway
|
||||||
if i.cur.Equal(i.high) {
|
}
|
||||||
i.cur = i.low
|
|
||||||
|
// If we've reached the end of this range, we need to advance the range
|
||||||
|
// RangeEnd is inclusive as well
|
||||||
|
if i.cur.Equal(r.RangeEnd) {
|
||||||
|
i.rangeIdx += 1
|
||||||
|
i.rangeIdx %= len(*i.rangeset)
|
||||||
|
r = (*i.rangeset)[i.rangeIdx]
|
||||||
|
|
||||||
|
i.cur = r.RangeStart
|
||||||
} else {
|
} else {
|
||||||
i.cur = ip.NextIP(i.cur)
|
i.cur = ip.NextIP(i.cur)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've looped back to where we started, exit
|
if i.startIP == nil {
|
||||||
if i.cur.Equal(i.start) {
|
i.startIP = i.cur
|
||||||
return nil
|
} else if i.rangeIdx == i.startRange && i.cur.Equal(i.startIP) {
|
||||||
|
// IF we've looped back to where we started, give up
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return i.cur
|
if i.cur.Equal(r.Gateway) {
|
||||||
|
return i.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &net.IPNet{IP: i.cur, Mask: r.Subnet.Mask}, r.Gateway
|
||||||
}
|
}
|
||||||
|
@ -21,29 +21,27 @@ import (
|
|||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
"github.com/containernetworking/cni/pkg/types/current"
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
fakestore "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing"
|
fakestore "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AllocatorTestCase struct {
|
type AllocatorTestCase struct {
|
||||||
subnet string
|
subnets []string
|
||||||
ipmap map[string]string
|
ipmap map[string]string
|
||||||
expectResult string
|
expectResult string
|
||||||
lastIP string
|
lastIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkalloc() IPAllocator {
|
func mkalloc() IPAllocator {
|
||||||
ipnet, _ := types.ParseCIDR("192.168.1.0/24")
|
p := RangeSet{
|
||||||
|
Range{Subnet: mustSubnet("192.168.1.0/29")},
|
||||||
r := Range{
|
|
||||||
Subnet: types.IPNet(*ipnet),
|
|
||||||
}
|
}
|
||||||
r.Canonicalize()
|
p.Canonicalize()
|
||||||
store := fakestore.NewFakeStore(map[string]string{}, map[string]net.IP{})
|
store := fakestore.NewFakeStore(map[string]string{}, map[string]net.IP{})
|
||||||
|
|
||||||
alloc := IPAllocator{
|
alloc := IPAllocator{
|
||||||
netName: "netname",
|
rangeset: &p,
|
||||||
ipRange: r,
|
|
||||||
store: store,
|
store: store,
|
||||||
rangeID: "rangeid",
|
rangeID: "rangeid",
|
||||||
}
|
}
|
||||||
@ -53,24 +51,23 @@ func mkalloc() IPAllocator {
|
|||||||
|
|
||||||
func (t AllocatorTestCase) run(idx int) (*current.IPConfig, error) {
|
func (t AllocatorTestCase) run(idx int) (*current.IPConfig, error) {
|
||||||
fmt.Fprintln(GinkgoWriter, "Index:", idx)
|
fmt.Fprintln(GinkgoWriter, "Index:", idx)
|
||||||
subnet, err := types.ParseCIDR(t.subnet)
|
p := RangeSet{}
|
||||||
|
for _, s := range t.subnets {
|
||||||
|
subnet, err := types.ParseCIDR(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
p = append(p, Range{Subnet: types.IPNet(*subnet)})
|
||||||
conf := Range{
|
|
||||||
Subnet: types.IPNet(*subnet),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect(conf.Canonicalize()).To(BeNil())
|
Expect(p.Canonicalize()).To(BeNil())
|
||||||
|
|
||||||
store := fakestore.NewFakeStore(t.ipmap, map[string]net.IP{"rangeid": net.ParseIP(t.lastIP)})
|
store := fakestore.NewFakeStore(t.ipmap, map[string]net.IP{"rangeid": net.ParseIP(t.lastIP)})
|
||||||
|
|
||||||
alloc := IPAllocator{
|
alloc := IPAllocator{
|
||||||
"netname",
|
rangeset: &p,
|
||||||
conf,
|
store: store,
|
||||||
store,
|
rangeID: "rangeid",
|
||||||
"rangeid",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return alloc.Get("ID", nil)
|
return alloc.Get("ID", nil)
|
||||||
@ -79,50 +76,40 @@ func (t AllocatorTestCase) run(idx int) (*current.IPConfig, error) {
|
|||||||
var _ = Describe("host-local ip allocator", func() {
|
var _ = Describe("host-local ip allocator", func() {
|
||||||
Context("RangeIter", func() {
|
Context("RangeIter", func() {
|
||||||
It("should loop correctly from the beginning", func() {
|
It("should loop correctly from the beginning", func() {
|
||||||
r := RangeIter{
|
a := mkalloc()
|
||||||
start: net.IP{10, 0, 0, 0},
|
r, _ := a.GetIter()
|
||||||
low: net.IP{10, 0, 0, 0},
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 2}))
|
||||||
high: net.IP{10, 0, 0, 5},
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 3}))
|
||||||
}
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 4}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 0}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 5}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 1}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 6}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 2}))
|
Expect(r.nextip()).To(BeNil())
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 3}))
|
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 4}))
|
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 5}))
|
|
||||||
Expect(r.Next()).To(BeNil())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should loop correctly from the end", func() {
|
It("should loop correctly from the end", func() {
|
||||||
r := RangeIter{
|
a := mkalloc()
|
||||||
start: net.IP{10, 0, 0, 5},
|
a.store.Reserve("ID", net.IP{192, 168, 1, 6}, a.rangeID)
|
||||||
low: net.IP{10, 0, 0, 0},
|
a.store.ReleaseByID("ID")
|
||||||
high: net.IP{10, 0, 0, 5},
|
r, _ := a.GetIter()
|
||||||
}
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 2}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 5}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 3}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 0}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 4}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 1}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 5}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 2}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 6}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 3}))
|
Expect(r.nextip()).To(BeNil())
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 4}))
|
|
||||||
Expect(r.Next()).To(BeNil())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should loop correctly from the middle", func() {
|
It("should loop correctly from the middle", func() {
|
||||||
r := RangeIter{
|
a := mkalloc()
|
||||||
start: net.IP{10, 0, 0, 3},
|
a.store.Reserve("ID", net.IP{192, 168, 1, 3}, a.rangeID)
|
||||||
low: net.IP{10, 0, 0, 0},
|
a.store.ReleaseByID("ID")
|
||||||
high: net.IP{10, 0, 0, 5},
|
r, _ := a.GetIter()
|
||||||
}
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 4}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 3}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 5}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 4}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 6}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 5}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 2}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 0}))
|
Expect(r.nextip()).To(Equal(net.IP{192, 168, 1, 3}))
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 1}))
|
Expect(r.nextip()).To(BeNil())
|
||||||
Expect(r.Next()).To(Equal(net.IP{10, 0, 0, 2}))
|
|
||||||
Expect(r.Next()).To(BeNil())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("when has free ip", func() {
|
Context("when has free ip", func() {
|
||||||
@ -130,25 +117,25 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
testCases := []AllocatorTestCase{
|
testCases := []AllocatorTestCase{
|
||||||
// fresh start
|
// fresh start
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/29",
|
subnets: []string{"10.0.0.0/29"},
|
||||||
ipmap: map[string]string{},
|
ipmap: map[string]string{},
|
||||||
expectResult: "10.0.0.2",
|
expectResult: "10.0.0.2",
|
||||||
lastIP: "",
|
lastIP: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
subnet: "2001:db8:1::0/64",
|
subnets: []string{"2001:db8:1::0/64"},
|
||||||
ipmap: map[string]string{},
|
ipmap: map[string]string{},
|
||||||
expectResult: "2001:db8:1::2",
|
expectResult: "2001:db8:1::2",
|
||||||
lastIP: "",
|
lastIP: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/30",
|
subnets: []string{"10.0.0.0/30"},
|
||||||
ipmap: map[string]string{},
|
ipmap: map[string]string{},
|
||||||
expectResult: "10.0.0.2",
|
expectResult: "10.0.0.2",
|
||||||
lastIP: "",
|
lastIP: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/29",
|
subnets: []string{"10.0.0.0/29"},
|
||||||
ipmap: map[string]string{
|
ipmap: map[string]string{
|
||||||
"10.0.0.2": "id",
|
"10.0.0.2": "id",
|
||||||
},
|
},
|
||||||
@ -157,13 +144,13 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
},
|
},
|
||||||
// next ip of last reserved ip
|
// next ip of last reserved ip
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/29",
|
subnets: []string{"10.0.0.0/29"},
|
||||||
ipmap: map[string]string{},
|
ipmap: map[string]string{},
|
||||||
expectResult: "10.0.0.6",
|
expectResult: "10.0.0.6",
|
||||||
lastIP: "10.0.0.5",
|
lastIP: "10.0.0.5",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/29",
|
subnets: []string{"10.0.0.0/29"},
|
||||||
ipmap: map[string]string{
|
ipmap: map[string]string{
|
||||||
"10.0.0.4": "id",
|
"10.0.0.4": "id",
|
||||||
"10.0.0.5": "id",
|
"10.0.0.5": "id",
|
||||||
@ -173,7 +160,7 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
},
|
},
|
||||||
// round robin to the beginning
|
// round robin to the beginning
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/29",
|
subnets: []string{"10.0.0.0/29"},
|
||||||
ipmap: map[string]string{
|
ipmap: map[string]string{
|
||||||
"10.0.0.6": "id",
|
"10.0.0.6": "id",
|
||||||
},
|
},
|
||||||
@ -182,16 +169,17 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
},
|
},
|
||||||
// lastIP is out of range
|
// lastIP is out of range
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/29",
|
subnets: []string{"10.0.0.0/29"},
|
||||||
ipmap: map[string]string{
|
ipmap: map[string]string{
|
||||||
"10.0.0.2": "id",
|
"10.0.0.2": "id",
|
||||||
},
|
},
|
||||||
expectResult: "10.0.0.3",
|
expectResult: "10.0.0.3",
|
||||||
lastIP: "10.0.0.128",
|
lastIP: "10.0.0.128",
|
||||||
},
|
},
|
||||||
|
// subnet is completely full except for lastip
|
||||||
// wrap around and reserve lastIP
|
// wrap around and reserve lastIP
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/29",
|
subnets: []string{"10.0.0.0/29"},
|
||||||
ipmap: map[string]string{
|
ipmap: map[string]string{
|
||||||
"10.0.0.2": "id",
|
"10.0.0.2": "id",
|
||||||
"10.0.0.4": "id",
|
"10.0.0.4": "id",
|
||||||
@ -201,6 +189,26 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
expectResult: "10.0.0.3",
|
expectResult: "10.0.0.3",
|
||||||
lastIP: "10.0.0.3",
|
lastIP: "10.0.0.3",
|
||||||
},
|
},
|
||||||
|
// alocate from multiple subnets
|
||||||
|
{
|
||||||
|
subnets: []string{"10.0.0.0/30", "10.0.1.0/30"},
|
||||||
|
expectResult: "10.0.0.2",
|
||||||
|
ipmap: map[string]string{},
|
||||||
|
},
|
||||||
|
// advance to next subnet
|
||||||
|
{
|
||||||
|
subnets: []string{"10.0.0.0/30", "10.0.1.0/30"},
|
||||||
|
lastIP: "10.0.0.2",
|
||||||
|
expectResult: "10.0.1.2",
|
||||||
|
ipmap: map[string]string{},
|
||||||
|
},
|
||||||
|
// Roll to start subnet
|
||||||
|
{
|
||||||
|
subnets: []string{"10.0.0.0/30", "10.0.1.0/30", "10.0.2.0/30"},
|
||||||
|
lastIP: "10.0.2.2",
|
||||||
|
expectResult: "10.0.0.2",
|
||||||
|
ipmap: map[string]string{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, tc := range testCases {
|
for idx, tc := range testCases {
|
||||||
@ -212,10 +220,10 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
|
|
||||||
It("should not allocate the broadcast address", func() {
|
It("should not allocate the broadcast address", func() {
|
||||||
alloc := mkalloc()
|
alloc := mkalloc()
|
||||||
for i := 2; i < 255; i++ {
|
for i := 2; i < 7; i++ {
|
||||||
res, err := alloc.Get("ID", nil)
|
res, err := alloc.Get("ID", nil)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
s := fmt.Sprintf("192.168.1.%d/24", i)
|
s := fmt.Sprintf("192.168.1.%d/29", i)
|
||||||
Expect(s).To(Equal(res.Address.String()))
|
Expect(s).To(Equal(res.Address.String()))
|
||||||
fmt.Fprintln(GinkgoWriter, "got ip", res.Address.String())
|
fmt.Fprintln(GinkgoWriter, "got ip", res.Address.String())
|
||||||
}
|
}
|
||||||
@ -229,44 +237,17 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
alloc := mkalloc()
|
alloc := mkalloc()
|
||||||
res, err := alloc.Get("ID", nil)
|
res, err := alloc.Get("ID", nil)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(res.Address.String()).To(Equal("192.168.1.2/24"))
|
Expect(res.Address.String()).To(Equal("192.168.1.2/29"))
|
||||||
|
|
||||||
err = alloc.Release("ID")
|
err = alloc.Release("ID")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
res, err = alloc.Get("ID", nil)
|
res, err = alloc.Get("ID", nil)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(res.Address.String()).To(Equal("192.168.1.3/24"))
|
Expect(res.Address.String()).To(Equal("192.168.1.3/29"))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should allocate RangeStart first", func() {
|
|
||||||
alloc := mkalloc()
|
|
||||||
alloc.ipRange.RangeStart = net.IP{192, 168, 1, 10}
|
|
||||||
res, err := alloc.Get("ID", nil)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(res.Address.String()).To(Equal("192.168.1.10/24"))
|
|
||||||
|
|
||||||
res, err = alloc.Get("ID", nil)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(res.Address.String()).To(Equal("192.168.1.11/24"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should allocate RangeEnd but not past RangeEnd", func() {
|
|
||||||
alloc := mkalloc()
|
|
||||||
alloc.ipRange.RangeEnd = net.IP{192, 168, 1, 5}
|
|
||||||
|
|
||||||
for i := 1; i < 5; i++ {
|
|
||||||
res, err := alloc.Get("ID", nil)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
// i+1 because the gateway address is skipped
|
|
||||||
Expect(res.Address.String()).To(Equal(fmt.Sprintf("192.168.1.%d/24", i+1)))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := alloc.Get("ID", nil)
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when requesting a specific IP", func() {
|
Context("when requesting a specific IP", func() {
|
||||||
It("must allocate the requested IP", func() {
|
It("must allocate the requested IP", func() {
|
||||||
alloc := mkalloc()
|
alloc := mkalloc()
|
||||||
@ -284,21 +265,21 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
Expect(res.Address.IP.String()).To(Equal(requestedIP.String()))
|
Expect(res.Address.IP.String()).To(Equal(requestedIP.String()))
|
||||||
|
|
||||||
_, err = alloc.Get("ID", requestedIP)
|
_, err = alloc.Get("ID", requestedIP)
|
||||||
Expect(err).To(MatchError(`requested IP address "192.168.1.5" is not available in network: netname 192.168.1.0/24`))
|
Expect(err).To(MatchError(`requested IP address 192.168.1.5 is not available in range set 192.168.1.1-192.168.1.6`))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("must return an error when the requested IP is after RangeEnd", func() {
|
It("must return an error when the requested IP is after RangeEnd", func() {
|
||||||
alloc := mkalloc()
|
alloc := mkalloc()
|
||||||
alloc.ipRange.RangeEnd = net.IP{192, 168, 1, 5}
|
(*alloc.rangeset)[0].RangeEnd = net.IP{192, 168, 1, 4}
|
||||||
requestedIP := net.IP{192, 168, 1, 6}
|
requestedIP := net.IP{192, 168, 1, 5}
|
||||||
_, err := alloc.Get("ID", requestedIP)
|
_, err := alloc.Get("ID", requestedIP)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("must return an error when the requested IP is before RangeStart", func() {
|
It("must return an error when the requested IP is before RangeStart", func() {
|
||||||
alloc := mkalloc()
|
alloc := mkalloc()
|
||||||
alloc.ipRange.RangeStart = net.IP{192, 168, 1, 6}
|
(*alloc.rangeset)[0].RangeStart = net.IP{192, 168, 1, 3}
|
||||||
requestedIP := net.IP{192, 168, 1, 5}
|
requestedIP := net.IP{192, 168, 1, 2}
|
||||||
_, err := alloc.Get("ID", requestedIP)
|
_, err := alloc.Get("ID", requestedIP)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
@ -309,28 +290,44 @@ var _ = Describe("host-local ip allocator", func() {
|
|||||||
It("returns a meaningful error", func() {
|
It("returns a meaningful error", func() {
|
||||||
testCases := []AllocatorTestCase{
|
testCases := []AllocatorTestCase{
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/30",
|
subnets: []string{"10.0.0.0/30"},
|
||||||
ipmap: map[string]string{
|
ipmap: map[string]string{
|
||||||
"10.0.0.2": "id",
|
"10.0.0.2": "id",
|
||||||
"10.0.0.3": "id",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
subnet: "10.0.0.0/29",
|
subnets: []string{"10.0.0.0/29"},
|
||||||
ipmap: map[string]string{
|
ipmap: map[string]string{
|
||||||
"10.0.0.2": "id",
|
"10.0.0.2": "id",
|
||||||
"10.0.0.3": "id",
|
"10.0.0.3": "id",
|
||||||
"10.0.0.4": "id",
|
"10.0.0.4": "id",
|
||||||
"10.0.0.5": "id",
|
"10.0.0.5": "id",
|
||||||
"10.0.0.6": "id",
|
"10.0.0.6": "id",
|
||||||
"10.0.0.7": "id",
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subnets: []string{"10.0.0.0/30", "10.0.1.0/30"},
|
||||||
|
ipmap: map[string]string{
|
||||||
|
"10.0.0.2": "id",
|
||||||
|
"10.0.1.2": "id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for idx, tc := range testCases {
|
for idx, tc := range testCases {
|
||||||
_, err := tc.run(idx)
|
_, err := tc.run(idx)
|
||||||
Expect(err).To(MatchError("no IP addresses available in network: netname " + tc.subnet))
|
Expect(err).NotTo(BeNil())
|
||||||
|
Expect(err.Error()).To(HavePrefix("no IP addresses available in range set"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// nextip is a convenience function used for testing
|
||||||
|
func (i *RangeIter) nextip() net.IP {
|
||||||
|
c, _ := i.Next()
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.IP
|
||||||
|
}
|
||||||
|
@ -23,6 +23,16 @@ import (
|
|||||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The top-level network config, just so we can get the IPAM block
|
||||||
|
type Net struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
CNIVersion string `json:"cniVersion"`
|
||||||
|
IPAM *IPAMConfig `json:"ipam"`
|
||||||
|
Args *struct {
|
||||||
|
A *IPAMArgs `json:"cni"`
|
||||||
|
} `json:"args"`
|
||||||
|
}
|
||||||
|
|
||||||
// IPAMConfig represents the IP related network configuration.
|
// IPAMConfig represents the IP related network configuration.
|
||||||
// This nests Range because we initially only supported a single
|
// This nests Range because we initially only supported a single
|
||||||
// range directly, and wish to preserve backwards compatability
|
// range directly, and wish to preserve backwards compatability
|
||||||
@ -33,7 +43,7 @@ type IPAMConfig struct {
|
|||||||
Routes []*types.Route `json:"routes"`
|
Routes []*types.Route `json:"routes"`
|
||||||
DataDir string `json:"dataDir"`
|
DataDir string `json:"dataDir"`
|
||||||
ResolvConf string `json:"resolvConf"`
|
ResolvConf string `json:"resolvConf"`
|
||||||
Ranges []Range `json:"ranges"`
|
Ranges []RangeSet `json:"ranges"`
|
||||||
IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS and args
|
IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS and args
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,15 +56,7 @@ type IPAMArgs struct {
|
|||||||
IPs []net.IP `json:"ips"`
|
IPs []net.IP `json:"ips"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The top-level network config, just so we can get the IPAM block
|
type RangeSet []Range
|
||||||
type Net struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
CNIVersion string `json:"cniVersion"`
|
|
||||||
IPAM *IPAMConfig `json:"ipam"`
|
|
||||||
Args *struct {
|
|
||||||
A *IPAMArgs `json:"cni"`
|
|
||||||
} `json:"args"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Range struct {
|
type Range struct {
|
||||||
RangeStart net.IP `json:"rangeStart,omitempty"` // The first ip, inclusive
|
RangeStart net.IP `json:"rangeStart,omitempty"` // The first ip, inclusive
|
||||||
@ -97,10 +99,10 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a single range (old-style config) is specified, move it to
|
// If a single range (old-style config) is specified, prepend it to
|
||||||
// the Ranges array
|
// the Ranges array
|
||||||
if n.IPAM.Range != nil && n.IPAM.Range.Subnet.IP != nil {
|
if n.IPAM.Range != nil && n.IPAM.Range.Subnet.IP != nil {
|
||||||
n.IPAM.Ranges = append([]Range{*n.IPAM.Range}, n.IPAM.Ranges...)
|
n.IPAM.Ranges = append([]RangeSet{{*n.IPAM.Range}}, n.IPAM.Ranges...)
|
||||||
}
|
}
|
||||||
n.IPAM.Range = nil
|
n.IPAM.Range = nil
|
||||||
|
|
||||||
@ -113,9 +115,10 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
|||||||
numV6 := 0
|
numV6 := 0
|
||||||
for i, _ := range n.IPAM.Ranges {
|
for i, _ := range n.IPAM.Ranges {
|
||||||
if err := n.IPAM.Ranges[i].Canonicalize(); err != nil {
|
if err := n.IPAM.Ranges[i].Canonicalize(); err != nil {
|
||||||
return nil, "", fmt.Errorf("Cannot understand range %d: %v", i, err)
|
return nil, "", fmt.Errorf("invalid range set %d: %s", i, err)
|
||||||
}
|
}
|
||||||
if len(n.IPAM.Ranges[i].RangeStart) == 4 {
|
|
||||||
|
if n.IPAM.Ranges[i][0].RangeStart.To4() != nil {
|
||||||
numV4++
|
numV4++
|
||||||
} else {
|
} else {
|
||||||
numV6++
|
numV6++
|
||||||
@ -126,17 +129,17 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
|||||||
if numV4 > 1 || numV6 > 1 {
|
if numV4 > 1 || numV6 > 1 {
|
||||||
for _, v := range types020.SupportedVersions {
|
for _, v := range types020.SupportedVersions {
|
||||||
if n.CNIVersion == v {
|
if n.CNIVersion == v {
|
||||||
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 range per address family", n.CNIVersion)
|
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for overlaps
|
// Check for overlaps
|
||||||
l := len(n.IPAM.Ranges)
|
l := len(n.IPAM.Ranges)
|
||||||
for i, r1 := range n.IPAM.Ranges[:l-1] {
|
for i, p1 := range n.IPAM.Ranges[:l-1] {
|
||||||
for j, r2 := range n.IPAM.Ranges[i+1:] {
|
for j, p2 := range n.IPAM.Ranges[i+1:] {
|
||||||
if r1.Overlaps(&r2) {
|
if p1.Overlaps(&p2) {
|
||||||
return nil, "", fmt.Errorf("Range %d overlaps with range %d", i, (i + j + 1))
|
return nil, "", fmt.Errorf("range set %d overlaps with %d", i, (i + j + 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,8 @@ var _ = Describe("IPAM config", func() {
|
|||||||
Expect(conf).To(Equal(&IPAMConfig{
|
Expect(conf).To(Equal(&IPAMConfig{
|
||||||
Name: "mynet",
|
Name: "mynet",
|
||||||
Type: "host-local",
|
Type: "host-local",
|
||||||
Ranges: []Range{
|
Ranges: []RangeSet{
|
||||||
|
RangeSet{
|
||||||
{
|
{
|
||||||
RangeStart: net.IP{10, 1, 2, 9},
|
RangeStart: net.IP{10, 1, 2, 9},
|
||||||
RangeEnd: net.IP{10, 1, 2, 20},
|
RangeEnd: net.IP{10, 1, 2, 20},
|
||||||
@ -55,8 +56,10 @@ var _ = Describe("IPAM config", func() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should parse a new-style config", func() {
|
It("Should parse a new-style config", func() {
|
||||||
input := `{
|
input := `{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
@ -66,6 +69,7 @@ var _ = Describe("IPAM config", func() {
|
|||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
|
[
|
||||||
{
|
{
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
"rangeStart": "10.1.2.9",
|
"rangeStart": "10.1.2.9",
|
||||||
@ -73,11 +77,15 @@ var _ = Describe("IPAM config", func() {
|
|||||||
"gateway": "10.1.2.30"
|
"gateway": "10.1.2.30"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"subnet": "10.1.4.0/24"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[{
|
||||||
"subnet": "11.1.2.0/24",
|
"subnet": "11.1.2.0/24",
|
||||||
"rangeStart": "11.1.2.9",
|
"rangeStart": "11.1.2.9",
|
||||||
"rangeEnd": "11.1.2.20",
|
"rangeEnd": "11.1.2.20",
|
||||||
"gateway": "11.1.2.30"
|
"gateway": "11.1.2.30"
|
||||||
}
|
}]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@ -88,7 +96,8 @@ var _ = Describe("IPAM config", func() {
|
|||||||
Expect(conf).To(Equal(&IPAMConfig{
|
Expect(conf).To(Equal(&IPAMConfig{
|
||||||
Name: "mynet",
|
Name: "mynet",
|
||||||
Type: "host-local",
|
Type: "host-local",
|
||||||
Ranges: []Range{
|
Ranges: []RangeSet{
|
||||||
|
{
|
||||||
{
|
{
|
||||||
RangeStart: net.IP{10, 1, 2, 9},
|
RangeStart: net.IP{10, 1, 2, 9},
|
||||||
RangeEnd: net.IP{10, 1, 2, 20},
|
RangeEnd: net.IP{10, 1, 2, 20},
|
||||||
@ -98,6 +107,17 @@ var _ = Describe("IPAM config", func() {
|
|||||||
Mask: net.CIDRMask(24, 32),
|
Mask: net.CIDRMask(24, 32),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
RangeStart: net.IP{10, 1, 4, 1},
|
||||||
|
RangeEnd: net.IP{10, 1, 4, 254},
|
||||||
|
Gateway: net.IP{10, 1, 4, 1},
|
||||||
|
Subnet: types.IPNet{
|
||||||
|
IP: net.IP{10, 1, 4, 0},
|
||||||
|
Mask: net.CIDRMask(24, 32),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
{
|
{
|
||||||
RangeStart: net.IP{11, 1, 2, 9},
|
RangeStart: net.IP{11, 1, 2, 9},
|
||||||
RangeEnd: net.IP{11, 1, 2, 20},
|
RangeEnd: net.IP{11, 1, 2, 20},
|
||||||
@ -108,6 +128,7 @@ var _ = Describe("IPAM config", func() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -123,14 +144,14 @@ var _ = Describe("IPAM config", func() {
|
|||||||
"rangeStart": "10.1.2.9",
|
"rangeStart": "10.1.2.9",
|
||||||
"rangeEnd": "10.1.2.20",
|
"rangeEnd": "10.1.2.20",
|
||||||
"gateway": "10.1.2.30",
|
"gateway": "10.1.2.30",
|
||||||
"ranges": [
|
"ranges": [[
|
||||||
{
|
{
|
||||||
"subnet": "11.1.2.0/24",
|
"subnet": "11.1.2.0/24",
|
||||||
"rangeStart": "11.1.2.9",
|
"rangeStart": "11.1.2.9",
|
||||||
"rangeEnd": "11.1.2.20",
|
"rangeEnd": "11.1.2.20",
|
||||||
"gateway": "11.1.2.30"
|
"gateway": "11.1.2.30"
|
||||||
}
|
}
|
||||||
]
|
]]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
conf, version, err := LoadIPAMConfig([]byte(input), "")
|
conf, version, err := LoadIPAMConfig([]byte(input), "")
|
||||||
@ -140,7 +161,8 @@ var _ = Describe("IPAM config", func() {
|
|||||||
Expect(conf).To(Equal(&IPAMConfig{
|
Expect(conf).To(Equal(&IPAMConfig{
|
||||||
Name: "mynet",
|
Name: "mynet",
|
||||||
Type: "host-local",
|
Type: "host-local",
|
||||||
Ranges: []Range{
|
Ranges: []RangeSet{
|
||||||
|
{
|
||||||
{
|
{
|
||||||
RangeStart: net.IP{10, 1, 2, 9},
|
RangeStart: net.IP{10, 1, 2, 9},
|
||||||
RangeEnd: net.IP{10, 1, 2, 20},
|
RangeEnd: net.IP{10, 1, 2, 20},
|
||||||
@ -150,6 +172,8 @@ var _ = Describe("IPAM config", func() {
|
|||||||
Mask: net.CIDRMask(24, 32),
|
Mask: net.CIDRMask(24, 32),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
{
|
{
|
||||||
RangeStart: net.IP{11, 1, 2, 9},
|
RangeStart: net.IP{11, 1, 2, 9},
|
||||||
RangeEnd: net.IP{11, 1, 2, 20},
|
RangeEnd: net.IP{11, 1, 2, 20},
|
||||||
@ -160,6 +184,7 @@ var _ = Describe("IPAM config", func() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -171,20 +196,14 @@ var _ = Describe("IPAM config", func() {
|
|||||||
"master": "foo0",
|
"master": "foo0",
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"ranges": [
|
"ranges": [[
|
||||||
{
|
{
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
"rangeStart": "10.1.2.9",
|
"rangeStart": "10.1.2.9",
|
||||||
"rangeEnd": "10.1.2.20",
|
"rangeEnd": "10.1.2.20",
|
||||||
"gateway": "10.1.2.30"
|
"gateway": "10.1.2.30"
|
||||||
},
|
|
||||||
{
|
|
||||||
"subnet": "11.1.2.0/24",
|
|
||||||
"rangeStart": "11.1.2.9",
|
|
||||||
"rangeEnd": "11.1.2.20",
|
|
||||||
"gateway": "11.1.2.30"
|
|
||||||
}
|
}
|
||||||
]
|
]]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@ -195,6 +214,7 @@ var _ = Describe("IPAM config", func() {
|
|||||||
Expect(conf.IPArgs).To(Equal([]net.IP{{10, 1, 2, 10}}))
|
Expect(conf.IPArgs).To(Equal([]net.IP{{10, 1, 2, 10}}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should parse config args", func() {
|
It("Should parse config args", func() {
|
||||||
input := `{
|
input := `{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
@ -209,21 +229,21 @@ var _ = Describe("IPAM config", func() {
|
|||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{
|
[{
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24",
|
||||||
"rangeStart": "10.1.2.9",
|
"rangeStart": "10.1.2.9",
|
||||||
"rangeEnd": "10.1.2.20",
|
"rangeEnd": "10.1.2.20",
|
||||||
"gateway": "10.1.2.30"
|
"gateway": "10.1.2.30"
|
||||||
},
|
}],
|
||||||
{
|
[{
|
||||||
"subnet": "11.1.2.0/24",
|
"subnet": "11.1.2.0/24",
|
||||||
"rangeStart": "11.1.2.9",
|
"rangeStart": "11.1.2.9",
|
||||||
"rangeEnd": "11.1.2.20",
|
"rangeEnd": "11.1.2.20",
|
||||||
"gateway": "11.1.2.30"
|
"gateway": "11.1.2.30"
|
||||||
},
|
}],
|
||||||
{
|
[{
|
||||||
"subnet": "2001:db8:1::/64"
|
"subnet": "2001:db8:1::/64"
|
||||||
}
|
}]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@ -239,7 +259,8 @@ var _ = Describe("IPAM config", func() {
|
|||||||
net.ParseIP("2001:db8:1::11"),
|
net.ParseIP("2001:db8:1::11"),
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
It("Should detect overlap", func() {
|
|
||||||
|
It("Should detect overlap between rangesets", func() {
|
||||||
input := `{
|
input := `{
|
||||||
"cniVersion": "0.3.1",
|
"cniVersion": "0.3.1",
|
||||||
"name": "mynet",
|
"name": "mynet",
|
||||||
@ -248,19 +269,62 @@ var _ = Describe("IPAM config", func() {
|
|||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{
|
[
|
||||||
"subnet": "10.1.2.0/24",
|
{"subnet": "10.1.2.0/24"},
|
||||||
"rangeEnd": "10.1.2.128"
|
{"subnet": "10.2.2.0/24"}
|
||||||
},
|
],
|
||||||
{
|
[
|
||||||
"subnet": "10.1.2.0/24",
|
{ "subnet": "10.1.4.0/24"},
|
||||||
"rangeStart": "10.1.2.15"
|
{ "subnet": "10.1.6.0/24"},
|
||||||
}
|
{ "subnet": "10.1.8.0/24"},
|
||||||
|
{ "subnet": "10.1.2.0/24"}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
_, _, err := LoadIPAMConfig([]byte(input), "")
|
_, _, err := LoadIPAMConfig([]byte(input), "")
|
||||||
Expect(err).To(MatchError("Range 0 overlaps with range 1"))
|
Expect(err).To(MatchError("range set 0 overlaps with 1"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should detect overlap within rangeset", func() {
|
||||||
|
input := `{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "foo0",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
{ "subnet": "10.1.0.0/22" },
|
||||||
|
{ "subnet": "10.1.2.0/24" }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
_, _, err := LoadIPAMConfig([]byte(input), "")
|
||||||
|
Expect(err).To(MatchError("invalid range set 0: subnets 10.1.0.1-10.1.3.254 and 10.1.2.1-10.1.2.254 overlap"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should error on rangesets with different families", func() {
|
||||||
|
input := `{
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"name": "mynet",
|
||||||
|
"type": "ipvlan",
|
||||||
|
"master": "foo0",
|
||||||
|
"ipam": {
|
||||||
|
"type": "host-local",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
{ "subnet": "10.1.0.0/22" },
|
||||||
|
{ "subnet": "2001:db8:5::/64" }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
_, _, err := LoadIPAMConfig([]byte(input), "")
|
||||||
|
Expect(err).To(MatchError("invalid range set 0: mixed address families"))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should should error on too many ranges", func() {
|
It("Should should error on too many ranges", func() {
|
||||||
@ -272,17 +336,13 @@ var _ = Describe("IPAM config", func() {
|
|||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{
|
[{"subnet": "10.1.2.0/24"}],
|
||||||
"subnet": "10.1.2.0/24"
|
[{"subnet": "11.1.2.0/24"}]
|
||||||
},
|
|
||||||
{
|
|
||||||
"subnet": "11.1.2.0/24"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
_, _, err := LoadIPAMConfig([]byte(input), "")
|
_, _, err := LoadIPAMConfig([]byte(input), "")
|
||||||
Expect(err).To(MatchError("CNI version 0.2.0 does not support more than 1 range per address family"))
|
Expect(err).To(MatchError("CNI version 0.2.0 does not support more than 1 address per family"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should allow one v4 and v6 range for 0.2.0", func() {
|
It("Should allow one v4 and v6 range for 0.2.0", func() {
|
||||||
@ -294,12 +354,8 @@ var _ = Describe("IPAM config", func() {
|
|||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{
|
[{"subnet": "10.1.2.0/24"}],
|
||||||
"subnet": "10.1.2.0/24"
|
[{"subnet": "2001:db8:1::/24"}]
|
||||||
},
|
|
||||||
{
|
|
||||||
"subnet": "2001:db8:1::/24"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
@ -61,8 +61,8 @@ func (r *Range) Canonicalize() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.IPInRange(r.RangeStart); err != nil {
|
if !r.Contains(r.RangeStart) {
|
||||||
return err
|
return fmt.Errorf("RangeStart %s not in network %s", r.RangeStart.String(), (*net.IPNet)(&r.Subnet).String())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
r.RangeStart = ip.NextIP(r.Subnet.IP)
|
r.RangeStart = ip.NextIP(r.Subnet.IP)
|
||||||
@ -75,8 +75,8 @@ func (r *Range) Canonicalize() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.IPInRange(r.RangeEnd); err != nil {
|
if !r.Contains(r.RangeEnd) {
|
||||||
return err
|
return fmt.Errorf("RangeEnd %s not in network %s", r.RangeEnd.String(), (*net.IPNet)(&r.Subnet).String())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
r.RangeEnd = lastIP(r.Subnet)
|
r.RangeEnd = lastIP(r.Subnet)
|
||||||
@ -86,38 +86,39 @@ func (r *Range) Canonicalize() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsValidIP checks if a given ip is a valid, allocatable address in a given Range
|
// IsValidIP checks if a given ip is a valid, allocatable address in a given Range
|
||||||
func (r *Range) IPInRange(addr net.IP) error {
|
func (r *Range) Contains(addr net.IP) bool {
|
||||||
if err := canonicalizeIP(&addr); err != nil {
|
if err := canonicalizeIP(&addr); err != nil {
|
||||||
return err
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
subnet := (net.IPNet)(r.Subnet)
|
subnet := (net.IPNet)(r.Subnet)
|
||||||
|
|
||||||
|
// Not the same address family
|
||||||
if len(addr) != len(r.Subnet.IP) {
|
if len(addr) != len(r.Subnet.IP) {
|
||||||
return fmt.Errorf("IP %s is not the same protocol as subnet %s",
|
return false
|
||||||
addr, subnet.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not in network
|
||||||
if !subnet.Contains(addr) {
|
if !subnet.Contains(addr) {
|
||||||
return fmt.Errorf("%s not in network %s", addr, subnet.String())
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// We ignore nils here so we can use this function as we initialize the range.
|
// We ignore nils here so we can use this function as we initialize the range.
|
||||||
if r.RangeStart != nil {
|
if r.RangeStart != nil {
|
||||||
|
// Before the range start
|
||||||
if ip.Cmp(addr, r.RangeStart) < 0 {
|
if ip.Cmp(addr, r.RangeStart) < 0 {
|
||||||
return fmt.Errorf("%s is in network %s but before start %s",
|
return false
|
||||||
addr, (*net.IPNet)(&r.Subnet).String(), r.RangeStart)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.RangeEnd != nil {
|
if r.RangeEnd != nil {
|
||||||
if ip.Cmp(addr, r.RangeEnd) > 0 {
|
if ip.Cmp(addr, r.RangeEnd) > 0 {
|
||||||
return fmt.Errorf("%s is in network %s but after end %s",
|
// After the range end
|
||||||
addr, (*net.IPNet)(&r.Subnet).String(), r.RangeEnd)
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overlaps returns true if there is any overlap between ranges
|
// Overlaps returns true if there is any overlap between ranges
|
||||||
@ -127,10 +128,14 @@ func (r *Range) Overlaps(r1 *Range) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.IPInRange(r1.RangeStart) == nil ||
|
return r.Contains(r1.RangeStart) ||
|
||||||
r.IPInRange(r1.RangeEnd) == nil ||
|
r.Contains(r1.RangeEnd) ||
|
||||||
r1.IPInRange(r.RangeStart) == nil ||
|
r1.Contains(r.RangeStart) ||
|
||||||
r1.IPInRange(r.RangeEnd) == nil
|
r1.Contains(r.RangeEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Range) String() string {
|
||||||
|
return fmt.Sprintf("%s-%s", r.RangeStart.String(), r.RangeEnd.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// canonicalizeIP makes sure a provided ip is in standard form
|
// canonicalizeIP makes sure a provided ip is in standard form
|
||||||
|
97
plugins/ipam/host-local/backend/allocator/range_set.go
Normal file
97
plugins/ipam/host-local/backend/allocator/range_set.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2017 CNI authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contains returns true if any range in this set contains an IP
|
||||||
|
func (s *RangeSet) Contains(addr net.IP) bool {
|
||||||
|
r, _ := s.RangeFor(addr)
|
||||||
|
return r != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeFor finds the range that contains an IP, or nil if not found
|
||||||
|
func (s *RangeSet) RangeFor(addr net.IP) (*Range, error) {
|
||||||
|
if err := canonicalizeIP(&addr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range *s {
|
||||||
|
if r.Contains(addr) {
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("%s not in range set %s", addr.String(), s.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlaps returns true if any ranges in any set overlap with this one
|
||||||
|
func (s *RangeSet) Overlaps(p1 *RangeSet) bool {
|
||||||
|
for _, r := range *s {
|
||||||
|
for _, r1 := range *p1 {
|
||||||
|
if r.Overlaps(&r1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonicalize ensures the RangeSet is in a standard form, and detects any
|
||||||
|
// invalid input. Call Range.Canonicalize() on every Range in the set
|
||||||
|
func (s *RangeSet) Canonicalize() error {
|
||||||
|
if len(*s) == 0 {
|
||||||
|
return fmt.Errorf("empty range set")
|
||||||
|
}
|
||||||
|
|
||||||
|
fam := 0
|
||||||
|
for i, _ := range *s {
|
||||||
|
if err := (*s)[i].Canonicalize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
fam = len((*s)[i].RangeStart)
|
||||||
|
} else {
|
||||||
|
if fam != len((*s)[i].RangeStart) {
|
||||||
|
return fmt.Errorf("mixed address families")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure none of the ranges in the set overlap
|
||||||
|
l := len(*s)
|
||||||
|
for i, r1 := range (*s)[:l-1] {
|
||||||
|
for _, r2 := range (*s)[i+1:] {
|
||||||
|
if r1.Overlaps(&r2) {
|
||||||
|
return fmt.Errorf("subnets %s and %s overlap", r1.String(), r2.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RangeSet) String() string {
|
||||||
|
out := []string{}
|
||||||
|
for _, r := range *s {
|
||||||
|
out = append(out, r.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(out, ",")
|
||||||
|
}
|
70
plugins/ipam/host-local/backend/allocator/range_set_test.go
Normal file
70
plugins/ipam/host-local/backend/allocator/range_set_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2017 CNI authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package allocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("range sets", func() {
|
||||||
|
It("should detect set membership correctly", func() {
|
||||||
|
p := RangeSet{
|
||||||
|
Range{Subnet: mustSubnet("192.168.0.0/24")},
|
||||||
|
Range{Subnet: mustSubnet("172.16.1.0/24")},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.Canonicalize()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(p.Contains(net.IP{192, 168, 0, 55})).To(BeTrue())
|
||||||
|
|
||||||
|
r, err := p.RangeFor(net.IP{192, 168, 0, 55})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(r).To(Equal(&p[0]))
|
||||||
|
|
||||||
|
r, err = p.RangeFor(net.IP{192, 168, 99, 99})
|
||||||
|
Expect(r).To(BeNil())
|
||||||
|
Expect(err).To(MatchError("192.168.99.99 not in range set 192.168.0.1-192.168.0.254,172.16.1.1-172.16.1.254"))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should discover overlaps within a set", func() {
|
||||||
|
p := RangeSet{
|
||||||
|
{Subnet: mustSubnet("192.168.0.0/20")},
|
||||||
|
{Subnet: mustSubnet("192.168.2.0/24")},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.Canonicalize()
|
||||||
|
Expect(err).To(MatchError("subnets 192.168.0.1-192.168.15.254 and 192.168.2.1-192.168.2.254 overlap"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should discover overlaps outside a set", func() {
|
||||||
|
p1 := RangeSet{
|
||||||
|
{Subnet: mustSubnet("192.168.0.0/20")},
|
||||||
|
}
|
||||||
|
p2 := RangeSet{
|
||||||
|
{Subnet: mustSubnet("192.168.2.0/24")},
|
||||||
|
}
|
||||||
|
|
||||||
|
p1.Canonicalize()
|
||||||
|
p2.Canonicalize()
|
||||||
|
|
||||||
|
Expect(p1.Overlaps(&p2)).To(BeTrue())
|
||||||
|
Expect(p2.Overlaps(&p1)).To(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
@ -77,11 +77,11 @@ var _ = Describe("IP ranges", func() {
|
|||||||
It("should reject invalid RangeStart and RangeEnd specifications", func() {
|
It("should reject invalid RangeStart and RangeEnd specifications", func() {
|
||||||
r := Range{Subnet: mustSubnet("192.0.2.0/24"), RangeStart: net.ParseIP("192.0.3.0")}
|
r := Range{Subnet: mustSubnet("192.0.2.0/24"), RangeStart: net.ParseIP("192.0.3.0")}
|
||||||
err := r.Canonicalize()
|
err := r.Canonicalize()
|
||||||
Expect(err).Should(MatchError("192.0.3.0 not in network 192.0.2.0/24"))
|
Expect(err).Should(MatchError("RangeStart 192.0.3.0 not in network 192.0.2.0/24"))
|
||||||
|
|
||||||
r = Range{Subnet: mustSubnet("192.0.2.0/24"), RangeEnd: net.ParseIP("192.0.4.0")}
|
r = Range{Subnet: mustSubnet("192.0.2.0/24"), RangeEnd: net.ParseIP("192.0.4.0")}
|
||||||
err = r.Canonicalize()
|
err = r.Canonicalize()
|
||||||
Expect(err).Should(MatchError("192.0.4.0 not in network 192.0.2.0/24"))
|
Expect(err).Should(MatchError("RangeEnd 192.0.4.0 not in network 192.0.2.0/24"))
|
||||||
|
|
||||||
r = Range{
|
r = Range{
|
||||||
Subnet: mustSubnet("192.0.2.0/24"),
|
Subnet: mustSubnet("192.0.2.0/24"),
|
||||||
@ -89,7 +89,7 @@ var _ = Describe("IP ranges", func() {
|
|||||||
RangeEnd: net.ParseIP("192.0.2.40"),
|
RangeEnd: net.ParseIP("192.0.2.40"),
|
||||||
}
|
}
|
||||||
err = r.Canonicalize()
|
err = r.Canonicalize()
|
||||||
Expect(err).Should(MatchError("192.0.2.50 is in network 192.0.2.0/24 but after end 192.0.2.40"))
|
Expect(err).Should(MatchError("RangeStart 192.0.2.50 not in network 192.0.2.0/24"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should reject invalid gateways", func() {
|
It("should reject invalid gateways", func() {
|
||||||
@ -126,15 +126,12 @@ var _ = Describe("IP ranges", func() {
|
|||||||
err := r.Canonicalize()
|
err := r.Canonicalize()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
Expect(r.IPInRange(net.ParseIP("192.0.3.0"))).Should(MatchError(
|
Expect(r.Contains(net.ParseIP("192.0.3.0"))).Should(BeFalse())
|
||||||
"192.0.3.0 not in network 192.0.2.0/24"))
|
|
||||||
|
|
||||||
Expect(r.IPInRange(net.ParseIP("192.0.2.39"))).Should(MatchError(
|
Expect(r.Contains(net.ParseIP("192.0.2.39"))).Should(BeFalse())
|
||||||
"192.0.2.39 is in network 192.0.2.0/24 but before start 192.0.2.40"))
|
Expect(r.Contains(net.ParseIP("192.0.2.40"))).Should(BeTrue())
|
||||||
Expect(r.IPInRange(net.ParseIP("192.0.2.40"))).Should(BeNil())
|
Expect(r.Contains(net.ParseIP("192.0.2.50"))).Should(BeTrue())
|
||||||
Expect(r.IPInRange(net.ParseIP("192.0.2.50"))).Should(BeNil())
|
Expect(r.Contains(net.ParseIP("192.0.2.51"))).Should(BeFalse())
|
||||||
Expect(r.IPInRange(net.ParseIP("192.0.2.51"))).Should(MatchError(
|
|
||||||
"192.0.2.51 is in network 192.0.2.0/24 but after end 192.0.2.50"))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should accept v6 IPs in range and reject IPs out of range", func() {
|
It("should accept v6 IPs in range and reject IPs out of range", func() {
|
||||||
@ -145,15 +142,12 @@ var _ = Describe("IP ranges", func() {
|
|||||||
}
|
}
|
||||||
err := r.Canonicalize()
|
err := r.Canonicalize()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(r.IPInRange(net.ParseIP("2001:db8:2::"))).Should(MatchError(
|
Expect(r.Contains(net.ParseIP("2001:db8:2::"))).Should(BeFalse())
|
||||||
"2001:db8:2:: not in network 2001:db8:1::/64"))
|
|
||||||
|
|
||||||
Expect(r.IPInRange(net.ParseIP("2001:db8:1::39"))).Should(MatchError(
|
Expect(r.Contains(net.ParseIP("2001:db8:1::39"))).Should(BeFalse())
|
||||||
"2001:db8:1::39 is in network 2001:db8:1::/64 but before start 2001:db8:1::40"))
|
Expect(r.Contains(net.ParseIP("2001:db8:1::40"))).Should(BeTrue())
|
||||||
Expect(r.IPInRange(net.ParseIP("2001:db8:1::40"))).Should(BeNil())
|
Expect(r.Contains(net.ParseIP("2001:db8:1::50"))).Should(BeTrue())
|
||||||
Expect(r.IPInRange(net.ParseIP("2001:db8:1::50"))).Should(BeNil())
|
Expect(r.Contains(net.ParseIP("2001:db8:1::51"))).Should(BeFalse())
|
||||||
Expect(r.IPInRange(net.ParseIP("2001:db8:1::51"))).Should(MatchError(
|
|
||||||
"2001:db8:1::51 is in network 2001:db8:1::/64 but after end 2001:db8:1::50"))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
DescribeTable("Detecting overlap",
|
DescribeTable("Detecting overlap",
|
||||||
|
@ -54,8 +54,8 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"resolvConf": "%s/resolv.conf",
|
"resolvConf": "%s/resolv.conf",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{ "subnet": "10.1.2.0/24" },
|
[{ "subnet": "10.1.2.0/24" }, {"subnet": "10.2.2.0/24"}],
|
||||||
{ "subnet": "2001:db8:1::0/64" }
|
[{ "subnet": "2001:db8:1::0/64" }]
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{"dst": "0.0.0.0/0"},
|
{"dst": "0.0.0.0/0"},
|
||||||
@ -117,12 +117,12 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(contents)).To(Equal("dummy"))
|
Expect(string(contents)).To(Equal("dummy"))
|
||||||
|
|
||||||
lastFilePath1 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.CgECAQ==")
|
lastFilePath1 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.0")
|
||||||
contents, err = ioutil.ReadFile(lastFilePath1)
|
contents, err = ioutil.ReadFile(lastFilePath1)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(contents)).To(Equal("10.1.2.2"))
|
Expect(string(contents)).To(Equal("10.1.2.2"))
|
||||||
|
|
||||||
lastFilePath2 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.IAENuAABAAAAAAAAAAAAAQ==")
|
lastFilePath2 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.1")
|
||||||
contents, err = ioutil.ReadFile(lastFilePath2)
|
contents, err = ioutil.ReadFile(lastFilePath2)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(contents)).To(Equal("2001:db8:1::2"))
|
Expect(string(contents)).To(Equal("2001:db8:1::2"))
|
||||||
@ -224,7 +224,7 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(contents)).To(Equal("dummy"))
|
Expect(string(contents)).To(Equal("dummy"))
|
||||||
|
|
||||||
lastFilePath := filepath.Join(tmpDir, "mynet", "last_reserved_ip.CgECAQ==")
|
lastFilePath := filepath.Join(tmpDir, "mynet", "last_reserved_ip.0")
|
||||||
contents, err = ioutil.ReadFile(lastFilePath)
|
contents, err = ioutil.ReadFile(lastFilePath)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(string(contents)).To(Equal("10.1.2.2"))
|
Expect(string(contents)).To(Equal("10.1.2.2"))
|
||||||
@ -344,7 +344,7 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{ "subnet": "10.1.2.0/24" }
|
[{ "subnet": "10.1.2.0/24" }]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
@ -392,8 +392,8 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{ "subnet": "10.1.2.0/24" },
|
[{ "subnet": "10.1.2.0/24" }],
|
||||||
{ "subnet": "10.1.3.0/24" }
|
[{ "subnet": "10.1.3.0/24" }]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
@ -442,8 +442,8 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{ "subnet": "10.1.2.0/24" },
|
[{"subnet":"172.16.1.0/24"}, { "subnet": "10.1.2.0/24" }],
|
||||||
{ "subnet": "2001:db8:1::/24" }
|
[{ "subnet": "2001:db8:1::/24" }]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
@ -489,8 +489,8 @@ var _ = Describe("host-local Operations", func() {
|
|||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"dataDir": "%s",
|
"dataDir": "%s",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{ "subnet": "10.1.2.0/24" },
|
[{ "subnet": "10.1.2.0/24" }],
|
||||||
{ "subnet": "10.1.3.0/24" }
|
[{ "subnet": "10.1.3.0/24" }]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
|
@ -66,13 +66,13 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
requestedIPs[ip.String()] = ip
|
requestedIPs[ip.String()] = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, ipRange := range ipamConf.Ranges {
|
for idx, rangeset := range ipamConf.Ranges {
|
||||||
allocator := allocator.NewIPAllocator(ipamConf.Name, ipRange, store)
|
allocator := allocator.NewIPAllocator(&rangeset, store, idx)
|
||||||
|
|
||||||
// Check to see if there are any custom IPs requested in this range.
|
// Check to see if there are any custom IPs requested in this range.
|
||||||
var requestedIP net.IP
|
var requestedIP net.IP
|
||||||
for k, ip := range requestedIPs {
|
for k, ip := range requestedIPs {
|
||||||
if ipRange.IPInRange(ip) == nil {
|
if rangeset.Contains(ip) {
|
||||||
requestedIP = ip
|
requestedIP = ip
|
||||||
delete(requestedIPs, k)
|
delete(requestedIPs, k)
|
||||||
break
|
break
|
||||||
@ -124,8 +124,8 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
|
|
||||||
// Loop through all ranges, releasing all IPs, even if an error occurs
|
// Loop through all ranges, releasing all IPs, even if an error occurs
|
||||||
var errors []string
|
var errors []string
|
||||||
for _, ipRange := range ipamConf.Ranges {
|
for idx, rangeset := range ipamConf.Ranges {
|
||||||
ipAllocator := allocator.NewIPAllocator(ipamConf.Name, ipRange, store)
|
ipAllocator := allocator.NewIPAllocator(&rangeset, store, idx)
|
||||||
|
|
||||||
err := ipAllocator.Release(args.ContainerID)
|
err := ipAllocator.Release(args.ContainerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -94,14 +94,14 @@ const (
|
|||||||
rangesStartStr = `,
|
rangesStartStr = `,
|
||||||
"ranges": [`
|
"ranges": [`
|
||||||
rangeSubnetConfStr = `
|
rangeSubnetConfStr = `
|
||||||
{
|
[{
|
||||||
"subnet": "%s"
|
"subnet": "%s"
|
||||||
}`
|
}]`
|
||||||
rangeSubnetGWConfStr = `
|
rangeSubnetGWConfStr = `
|
||||||
{
|
[{
|
||||||
"subnet": "%s",
|
"subnet": "%s",
|
||||||
"gateway": "%s"
|
"gateway": "%s"
|
||||||
}`
|
}]`
|
||||||
rangesEndStr = `
|
rangesEndStr = `
|
||||||
]`
|
]`
|
||||||
|
|
||||||
|
@ -155,8 +155,8 @@ var _ = Describe("ptp Operations", func() {
|
|||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"ranges": [
|
"ranges": [
|
||||||
{ "subnet": "10.1.2.0/24"},
|
[{ "subnet": "10.1.2.0/24"}],
|
||||||
{ "subnet": "2001:db8:1::0/66"}
|
[{ "subnet": "2001:db8:1::0/66"}]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user