Files
kubesphere/vendor/github.com/projectcalico/libcalico-go/lib/ipam/ipam_block.go
2019-08-17 15:34:02 +08:00

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
}