409 lines
12 KiB
Go
409 lines
12 KiB
Go
// Copyright (c) 2016-2019 Tigera, Inc. All rights reserved.
|
|
|
|
// 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 ipam
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/projectcalico/libcalico-go/lib/apis/v3"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/projectcalico/libcalico-go/lib/backend/model"
|
|
cnet "github.com/projectcalico/libcalico-go/lib/net"
|
|
)
|
|
|
|
// Wrap the backend AllocationBlock struct so that we can
|
|
// attach methods to it.
|
|
type allocationBlock struct {
|
|
*model.AllocationBlock
|
|
}
|
|
|
|
func newBlock(cidr cnet.IPNet) allocationBlock {
|
|
ones, size := cidr.Mask.Size()
|
|
numAddresses := 1 << uint(size-ones)
|
|
b := model.AllocationBlock{}
|
|
b.Allocations = make([]*int, numAddresses)
|
|
b.Unallocated = make([]int, numAddresses)
|
|
b.StrictAffinity = false
|
|
b.CIDR = cidr
|
|
|
|
// Initialize unallocated ordinals.
|
|
for i := 0; i < numAddresses; i++ {
|
|
b.Unallocated[i] = i
|
|
}
|
|
|
|
return allocationBlock{&b}
|
|
}
|
|
|
|
func (b *allocationBlock) autoAssign(
|
|
num int, handleID *string, host string, attrs map[string]string, affinityCheck bool) ([]cnet.IPNet, error) {
|
|
|
|
// Determine if we need to check for affinity.
|
|
checkAffinity := b.StrictAffinity || affinityCheck
|
|
if checkAffinity && b.Affinity != nil && !hostAffinityMatches(host, b.AllocationBlock) {
|
|
// Affinity check is enabled but the host does not match - error.
|
|
s := fmt.Sprintf("Block affinity (%s) does not match provided (%s)", *b.Affinity, host)
|
|
return nil, errors.New(s)
|
|
} else if b.Affinity == nil {
|
|
log.Warnf("Attempting to assign IPs from block with no affinity: %v", b)
|
|
if checkAffinity {
|
|
// If we're checking strict affinity, we can't assign from a block with no affinity.
|
|
return nil, fmt.Errorf("Attempt to assign from block %v with no affinity", b.CIDR)
|
|
}
|
|
}
|
|
|
|
// Walk the allocations until we find enough addresses.
|
|
ordinals := []int{}
|
|
for len(b.Unallocated) > 0 && len(ordinals) < num {
|
|
ordinals = append(ordinals, b.Unallocated[0])
|
|
b.Unallocated = b.Unallocated[1:]
|
|
}
|
|
|
|
// Create slice of IPs and perform the allocations.
|
|
ips := []cnet.IPNet{}
|
|
_, mask, _ := cnet.ParseCIDR(b.CIDR.String())
|
|
for _, o := range ordinals {
|
|
attrIndex := b.findOrAddAttribute(handleID, attrs)
|
|
b.Allocations[o] = &attrIndex
|
|
ipNets := cnet.IPNet(*mask)
|
|
ipNets.IP = cnet.IncrementIP(cnet.IP{b.CIDR.IP}, big.NewInt(int64(o))).IP
|
|
ips = append(ips, ipNets)
|
|
}
|
|
|
|
log.Debugf("Block %s returned ips: %v", b.CIDR.String(), ips)
|
|
return ips, nil
|
|
}
|
|
|
|
func (b *allocationBlock) assign(address cnet.IP, handleID *string, attrs map[string]string, host string) error {
|
|
if b.StrictAffinity && b.Affinity != nil && !hostAffinityMatches(host, b.AllocationBlock) {
|
|
// Affinity check is enabled but the host does not match - error.
|
|
return errors.New("Block host affinity does not match")
|
|
} else if b.Affinity == nil {
|
|
log.Warnf("Attempting to assign IP from block with no affinity: %v", b)
|
|
if b.StrictAffinity {
|
|
// If we're checking strict affinity, we can't assign from a block with no affinity.
|
|
return fmt.Errorf("Attempt to assign from block %v with no affinity", b.CIDR)
|
|
}
|
|
}
|
|
|
|
// Convert to an ordinal.
|
|
ordinal, err := b.IPToOrdinal(address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if already allocated.
|
|
if b.Allocations[ordinal] != nil {
|
|
return errors.New("Address already assigned in block")
|
|
}
|
|
|
|
// Set up attributes.
|
|
attrIndex := b.findOrAddAttribute(handleID, attrs)
|
|
b.Allocations[ordinal] = &attrIndex
|
|
|
|
// Remove from unallocated.
|
|
for i, unallocated := range b.Unallocated {
|
|
if unallocated == ordinal {
|
|
b.Unallocated = append(b.Unallocated[:i], b.Unallocated[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// hostAffinityMatches checks if the provided host matches the provided affinity.
|
|
func hostAffinityMatches(host string, block *model.AllocationBlock) bool {
|
|
return *block.Affinity == "host:"+host
|
|
}
|
|
|
|
func getHostAffinity(block *model.AllocationBlock) string {
|
|
if block.Affinity != nil && strings.HasPrefix(*block.Affinity, "host:") {
|
|
return strings.TrimPrefix(*block.Affinity, "host:")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (b allocationBlock) numFreeAddresses() int {
|
|
return len(b.Unallocated)
|
|
}
|
|
|
|
func (b allocationBlock) empty() bool {
|
|
return b.numFreeAddresses() == b.NumAddresses()
|
|
}
|
|
|
|
func (b *allocationBlock) release(addresses []cnet.IP) ([]cnet.IP, map[string]int, error) {
|
|
// Store return values.
|
|
unallocated := []cnet.IP{}
|
|
countByHandle := map[string]int{}
|
|
|
|
// Used internally.
|
|
var ordinals []int
|
|
delRefCounts := map[int]int{}
|
|
attrsToDelete := []int{}
|
|
|
|
// De-duplicate addresses to ensure reference counting is correcet
|
|
uniqueAddresses := make(map[string]struct{})
|
|
for _, ip := range addresses {
|
|
uniqueAddresses[ip.IP.String()] = struct{}{}
|
|
}
|
|
|
|
// Determine the ordinals that need to be released and the
|
|
// attributes that need to be cleaned up.
|
|
log.Debugf("Releasing addresses from block: %v", uniqueAddresses)
|
|
for ipStr := range uniqueAddresses {
|
|
ip := cnet.MustParseIP(ipStr)
|
|
// Convert to an ordinal.
|
|
ordinal, err := b.IPToOrdinal(ip)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
log.Debugf("Address %s is ordinal %d", ip, ordinal)
|
|
|
|
// Check if allocated.
|
|
log.Debugf("Checking if allocated: %v", b.Allocations)
|
|
attrIdx := b.Allocations[ordinal]
|
|
if attrIdx == nil {
|
|
log.Debugf("Asked to release address that was not allocated")
|
|
unallocated = append(unallocated, ip)
|
|
continue
|
|
}
|
|
ordinals = append(ordinals, ordinal)
|
|
log.Debugf("%s is allocated, ordinals to release are now %v", ip, ordinals)
|
|
|
|
// Increment reference counting for attributes.
|
|
cnt := 1
|
|
if cur, exists := delRefCounts[*attrIdx]; exists {
|
|
cnt = cur + 1
|
|
}
|
|
delRefCounts[*attrIdx] = cnt
|
|
log.Debugf("delRefCounts: %v", delRefCounts)
|
|
|
|
// Increment count of addresses by handle if a handle
|
|
// exists.
|
|
log.Debugf("Looking up attribute with index %d", *attrIdx)
|
|
handleID := b.Attributes[*attrIdx].AttrPrimary
|
|
if handleID != nil {
|
|
log.Debugf("HandleID is %s", *handleID)
|
|
handleCount := 0
|
|
if count, ok := countByHandle[*handleID]; !ok {
|
|
handleCount = count
|
|
}
|
|
log.Debugf("Handle ref count is %d, incrementing", handleCount)
|
|
handleCount += 1
|
|
countByHandle[*handleID] = handleCount
|
|
log.Debugf("countByHandle %v", countByHandle)
|
|
}
|
|
}
|
|
|
|
// Handle cleaning up of attributes. We do this by
|
|
// reference counting. If we're deleting the last reference to
|
|
// a given attribute, then it needs to be cleaned up.
|
|
refCounts := b.attributeRefCounts()
|
|
log.Debugf("Cleaning up attributes, refCounts: %v", refCounts)
|
|
for idx, refs := range delRefCounts {
|
|
log.Debugf("Checking ref count index %d", idx)
|
|
if refCounts[idx] == refs {
|
|
attrsToDelete = append(attrsToDelete, idx)
|
|
}
|
|
}
|
|
if len(attrsToDelete) != 0 {
|
|
log.Debugf("Deleting attributes: %v", attrsToDelete)
|
|
b.deleteAttributes(attrsToDelete, ordinals)
|
|
}
|
|
|
|
// Release requested addresses.
|
|
log.Debugf("Allocations: %v", b.Allocations)
|
|
log.Debugf("Releasing ordinals: %v", ordinals)
|
|
for _, ordinal := range ordinals {
|
|
log.Debugf("Releasing ordinal %d", ordinal)
|
|
b.Allocations[ordinal] = nil
|
|
b.Unallocated = append(b.Unallocated, ordinal)
|
|
}
|
|
return unallocated, countByHandle, nil
|
|
}
|
|
|
|
func (b *allocationBlock) deleteAttributes(delIndexes, ordinals []int) {
|
|
newIndexes := make([]*int, len(b.Attributes))
|
|
newAttrs := []model.AllocationAttribute{}
|
|
y := 0 // Next free slot in the new attributes list.
|
|
for x := range b.Attributes {
|
|
if !intInSlice(x, delIndexes) {
|
|
// Attribute at x is not being deleted. Build a mapping
|
|
// of old attribute index (x) to new attribute index (y).
|
|
log.Debugf("%d in %v", x, delIndexes)
|
|
newIndex := y
|
|
newIndexes[x] = &newIndex
|
|
y += 1
|
|
newAttrs = append(newAttrs, b.Attributes[x])
|
|
}
|
|
}
|
|
b.Attributes = newAttrs
|
|
|
|
// Update attribute indexes for all allocations in this block.
|
|
for i := 0; i < b.NumAddresses(); i++ {
|
|
if b.Allocations[i] != nil {
|
|
// Get the new index that corresponds to the old index
|
|
// and update the allocation.
|
|
newIndex := newIndexes[*b.Allocations[i]]
|
|
b.Allocations[i] = newIndex
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b allocationBlock) attributeRefCounts() map[int]int {
|
|
refCounts := map[int]int{}
|
|
for _, a := range b.Allocations {
|
|
if a == nil {
|
|
continue
|
|
}
|
|
|
|
if count, ok := refCounts[*a]; !ok {
|
|
// No entry for given attribute index.
|
|
refCounts[*a] = 1
|
|
} else {
|
|
refCounts[*a] = count + 1
|
|
}
|
|
}
|
|
return refCounts
|
|
}
|
|
|
|
func (b allocationBlock) attributeIndexesByHandle(handleID string) []int {
|
|
indexes := []int{}
|
|
for i, attr := range b.Attributes {
|
|
if attr.AttrPrimary != nil && *attr.AttrPrimary == handleID {
|
|
indexes = append(indexes, i)
|
|
}
|
|
}
|
|
return indexes
|
|
}
|
|
|
|
func (b *allocationBlock) releaseByHandle(handleID string) int {
|
|
attrIndexes := b.attributeIndexesByHandle(handleID)
|
|
log.Debugf("Attribute indexes to release: %v", attrIndexes)
|
|
if len(attrIndexes) == 0 {
|
|
// Nothing to release.
|
|
log.Debugf("No addresses assigned to handle '%s'", handleID)
|
|
return 0
|
|
}
|
|
|
|
// There are addresses to release.
|
|
ordinals := []int{}
|
|
var o int
|
|
for o = 0; o < b.NumAddresses(); o++ {
|
|
// Only check allocated ordinals.
|
|
if b.Allocations[o] != nil && intInSlice(*b.Allocations[o], attrIndexes) {
|
|
// Release this ordinal.
|
|
ordinals = append(ordinals, o)
|
|
}
|
|
}
|
|
|
|
// Clean and reorder attributes.
|
|
b.deleteAttributes(attrIndexes, ordinals)
|
|
|
|
// Release the addresses.
|
|
for _, o := range ordinals {
|
|
b.Allocations[o] = nil
|
|
b.Unallocated = append(b.Unallocated, o)
|
|
}
|
|
return len(ordinals)
|
|
}
|
|
|
|
func (b allocationBlock) ipsByHandle(handleID string) []cnet.IP {
|
|
ips := []cnet.IP{}
|
|
attrIndexes := b.attributeIndexesByHandle(handleID)
|
|
var o int
|
|
for o = 0; o < b.NumAddresses(); o++ {
|
|
if b.Allocations[o] != nil && intInSlice(*b.Allocations[o], attrIndexes) {
|
|
ip := b.OrdinalToIP(o)
|
|
ips = append(ips, ip)
|
|
}
|
|
}
|
|
return ips
|
|
}
|
|
|
|
func (b allocationBlock) attributesForIP(ip cnet.IP) (map[string]string, error) {
|
|
// Convert to an ordinal.
|
|
ordinal, err := b.IPToOrdinal(ip)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check if allocated.
|
|
attrIndex := b.Allocations[ordinal]
|
|
if attrIndex == nil {
|
|
return nil, errors.New(fmt.Sprintf("IP %s is not currently assigned in block", ip))
|
|
}
|
|
return b.Attributes[*attrIndex].AttrSecondary, nil
|
|
}
|
|
|
|
func (b *allocationBlock) findOrAddAttribute(handleID *string, attrs map[string]string) int {
|
|
logCtx := log.WithField("attrs", attrs)
|
|
if handleID != nil {
|
|
logCtx = log.WithField("handle", *handleID)
|
|
}
|
|
attr := model.AllocationAttribute{handleID, attrs}
|
|
for idx, existing := range b.Attributes {
|
|
if reflect.DeepEqual(attr, existing) {
|
|
log.Debugf("Attribute '%+v' already exists", attr)
|
|
return idx
|
|
}
|
|
}
|
|
|
|
// Does not exist - add it.
|
|
logCtx.Debugf("New allocation attribute: %#v", attr)
|
|
attrIndex := len(b.Attributes)
|
|
b.Attributes = append(b.Attributes, attr)
|
|
return attrIndex
|
|
}
|
|
|
|
func getBlockCIDRForAddress(addr cnet.IP, pool *v3.IPPool) cnet.IPNet {
|
|
var mask net.IPMask
|
|
if addr.Version() == 6 {
|
|
// This is an IPv6 address.
|
|
mask = net.CIDRMask(pool.Spec.BlockSize, 128)
|
|
} else {
|
|
// This is an IPv4 address.
|
|
mask = net.CIDRMask(pool.Spec.BlockSize, 32)
|
|
}
|
|
masked := addr.Mask(mask)
|
|
return cnet.IPNet{IPNet: net.IPNet{IP: masked, Mask: mask}}
|
|
}
|
|
|
|
func getIPVersion(ip cnet.IP) int {
|
|
if ip.To4() == nil {
|
|
return 6
|
|
}
|
|
return 4
|
|
}
|
|
|
|
func largerThanOrEqualToBlock(blockCIDR cnet.IPNet, pool *v3.IPPool) bool {
|
|
ones, _ := blockCIDR.Mask.Size()
|
|
return ones <= pool.Spec.BlockSize
|
|
}
|
|
|
|
func intInSlice(searchInt int, slice []int) bool {
|
|
for _, v := range slice {
|
|
if v == searchInt {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|