implement ippool
1. support vlan ippool management 2. support calico ippool management Signed-off-by: Duan Jiong <djduanjiong@gmail.com>
This commit is contained in:
47
pkg/simple/client/network/ippool/ipam/interface.go
Normal file
47
pkg/simple/client/network/ippool/ipam/interface.go
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2020 KubeSphere 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 ipam
|
||||
|
||||
import (
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
||||
)
|
||||
|
||||
// ipam.Interface has methods to perform IP address management.
|
||||
type Interface interface {
|
||||
// AutoAssign automatically assigns one or more IP addresses as specified by the
|
||||
// provided AutoAssignArgs. AutoAssign returns the list of the assigned IPv4 addresses,
|
||||
// and the list of the assigned IPv6 addresses in IPNet format.
|
||||
// The returned IPNet represents the allocation block from which the IP was allocated,
|
||||
// which is useful for dataplanes that need to know the subnet (such as Windows).
|
||||
//
|
||||
// In case of error, returns the IPs allocated so far along with the error.
|
||||
AutoAssign(args AutoAssignArgs) (*current.Result, error)
|
||||
|
||||
// ReleaseByHandle releases all IP addresses that have been assigned
|
||||
// using the provided handle. Returns an error if no addresses
|
||||
// are assigned with the given handle.
|
||||
ReleaseByHandle(handleID string) error
|
||||
|
||||
GetUtilization(args GetUtilizationArgs) ([]*PoolUtilization, error)
|
||||
}
|
||||
|
||||
// Interface used to access the enabled IPPools.
|
||||
type PoolAccessorInterface interface {
|
||||
// Returns a list of all pools sorted in alphanumeric name order.
|
||||
getAllPools() ([]v1alpha1.IPPool, error)
|
||||
}
|
||||
626
pkg/simple/client/network/ippool/ipam/ipam.go
Normal file
626
pkg/simple/client/network/ippool/ipam/ipam.go
Normal file
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
Copyright 2020 KubeSphere 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 ipam
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
|
||||
cnitypes "github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
cnet "github.com/projectcalico/libcalico-go/lib/net"
|
||||
"github.com/projectcalico/libcalico-go/lib/set"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
||||
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/network/utils"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// Number of retries when we have an error writing data to etcd.
|
||||
datastoreRetries = 10
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoQualifiedPool = errors.New("cannot find a qualified ippool")
|
||||
ErrNoFreeBlocks = errors.New("no free blocks in ippool")
|
||||
ErrMaxRetry = errors.New("Max retries hit - excessive concurrent IPAM requests")
|
||||
ErrUnknowIPPoolType = errors.New("unknow ippool type")
|
||||
)
|
||||
|
||||
func (c IPAMClient) getAllPools() ([]v1alpha1.IPPool, error) {
|
||||
pools, err := c.client.NetworkV1alpha1().IPPools().List(metav1.ListOptions{
|
||||
LabelSelector: labels.SelectorFromSet(labels.Set{
|
||||
v1alpha1.IPPoolTypeLabel: c.typeStr,
|
||||
}).String(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pools.Items, nil
|
||||
}
|
||||
|
||||
// NewIPAMClient returns a new IPAMClient, which implements Interface.
|
||||
func NewIPAMClient(client kubesphere.Interface, typeStr string) IPAMClient {
|
||||
return IPAMClient{
|
||||
typeStr: typeStr,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// IPAMClient implements Interface
|
||||
type IPAMClient struct {
|
||||
typeStr string
|
||||
client kubesphere.Interface
|
||||
}
|
||||
|
||||
// AutoAssign automatically assigns one or more IP addresses as specified by the
|
||||
// provided AutoAssignArgs. AutoAssign returns the list of the assigned IPv4 addresses,
|
||||
// and the list of the assigned IPv6 addresses.
|
||||
//
|
||||
// In case of error, returns the IPs allocated so far along with the error.
|
||||
func (c IPAMClient) AutoAssign(args AutoAssignArgs) (*current.Result, error) {
|
||||
var (
|
||||
result current.Result
|
||||
err error
|
||||
ip *cnet.IPNet
|
||||
pool *v1alpha1.IPPool
|
||||
)
|
||||
|
||||
for i := 0; i < datastoreRetries; i++ {
|
||||
pool, err = c.client.NetworkV1alpha1().IPPools().Get(args.Pool, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, ErrNoQualifiedPool
|
||||
}
|
||||
|
||||
if pool.Disabled() {
|
||||
klog.Infof("provided ippool %s should be enabled", pool.Name)
|
||||
return nil, ErrNoQualifiedPool
|
||||
}
|
||||
|
||||
if pool.TypeInvalid() {
|
||||
return nil, ErrUnknowIPPoolType
|
||||
}
|
||||
|
||||
ip, err = c.autoAssign(args.HandleID, args.Attrs, pool)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNoFreeBlocks) {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
klog.Infof("AutoAssign: args=%s, err=%v", spew.Sdump(args), err)
|
||||
return nil, ErrMaxRetry
|
||||
}
|
||||
|
||||
version := 4
|
||||
if ip.IP.To4() == nil {
|
||||
version = 6
|
||||
}
|
||||
|
||||
result.IPs = append(result.IPs, ¤t.IPConfig{
|
||||
Version: fmt.Sprintf("%d", version),
|
||||
Address: net.IPNet{IP: ip.IP, Mask: ip.Mask},
|
||||
Gateway: net.ParseIP(pool.Spec.Gateway),
|
||||
})
|
||||
|
||||
for _, route := range pool.Spec.Routes {
|
||||
_, dst, _ := net.ParseCIDR(route.Dst)
|
||||
result.Routes = append(result.Routes, &cnitypes.Route{
|
||||
Dst: *dst,
|
||||
GW: net.ParseIP(route.GW),
|
||||
})
|
||||
}
|
||||
result.DNS.Domain = pool.Spec.DNS.Domain
|
||||
result.DNS.Options = pool.Spec.DNS.Options
|
||||
result.DNS.Nameservers = pool.Spec.DNS.Nameservers
|
||||
result.DNS.Search = pool.Spec.DNS.Search
|
||||
|
||||
poolType := pool.Spec.Type
|
||||
switch poolType {
|
||||
case v1alpha1.VLAN:
|
||||
result.Interfaces = append(result.Interfaces, ¤t.Interface{
|
||||
Mac: utils.EthRandomAddr(ip.IP),
|
||||
})
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
//findOrClaimBlock find an address block with free space, and if it doesn't exist, create it.
|
||||
func (c IPAMClient) findOrClaimBlock(pool *v1alpha1.IPPool, minFreeIps int) (*v1alpha1.IPAMBlock, error) {
|
||||
remainingBlocks, err := c.ListBlocks(pool.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First, we try to find a block from one of the existing blocks.
|
||||
for len(remainingBlocks) > 0 {
|
||||
// Pop first cidr.
|
||||
block := remainingBlocks[0]
|
||||
remainingBlocks = remainingBlocks[1:]
|
||||
|
||||
// Pull out the block.
|
||||
if block.NumFreeAddresses() >= minFreeIps {
|
||||
return &block, nil
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//Second, create unused Address Blocks
|
||||
b, err := c.findUnclaimedBlock(pool)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
controllerutil.SetControllerReference(pool, b, scheme.Scheme)
|
||||
b, err = c.client.NetworkV1alpha1().IPAMBlocks().Create(b)
|
||||
if err != nil {
|
||||
if k8serrors.IsAlreadyExists(err) {
|
||||
b, err = c.queryBlock(b.BlockName())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if b.NumFreeAddresses() >= minFreeIps {
|
||||
return b, nil
|
||||
} else {
|
||||
errString := fmt.Sprintf("Block '%s' has %d free ips which is less than %d ips required.", b.BlockName(), b.NumFreeAddresses(), minFreeIps)
|
||||
return nil, errors.New(errString)
|
||||
}
|
||||
}
|
||||
|
||||
func (c IPAMClient) autoAssign(handleID string, attrs map[string]string, requestedPool *v1alpha1.IPPool) (*cnet.IPNet, error) {
|
||||
var (
|
||||
result *cnet.IPNet
|
||||
)
|
||||
|
||||
b, err := c.findOrClaimBlock(requestedPool, 1)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
for i := 0; i < datastoreRetries; i++ {
|
||||
result, err = c.assignFromExistingBlock(b, handleID, attrs)
|
||||
if err != nil {
|
||||
if k8serrors.IsConflict(err) {
|
||||
b, err = c.queryBlock(b.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Block b is in sync with datastore. Retry assigning IP.
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return nil, ErrMaxRetry
|
||||
}
|
||||
|
||||
func (c IPAMClient) assignFromExistingBlock(block *v1alpha1.IPAMBlock, handleID string, attrs map[string]string) (*cnet.IPNet, error) {
|
||||
ips := block.AutoAssign(1, handleID, attrs)
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("block %s has no availabe IP", block.BlockName())
|
||||
}
|
||||
|
||||
err := c.incrementHandle(handleID, block, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = c.client.NetworkV1alpha1().IPAMBlocks().Update(block)
|
||||
if err != nil {
|
||||
if err := c.decrementHandle(handleID, block, 1); err != nil {
|
||||
klog.Errorf("Failed to decrement handle %s", handleID)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ips[0], nil
|
||||
}
|
||||
|
||||
// ReleaseByHandle releases all IP addresses that have been assigned
|
||||
// using the provided handle.
|
||||
func (c IPAMClient) ReleaseByHandle(handleID string) error {
|
||||
handle, err := c.queryHandle(handleID)
|
||||
if err != nil {
|
||||
if k8serrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for blockStr, _ := range handle.Spec.Block {
|
||||
blockName := v1alpha1.ConvertToBlockName(blockStr)
|
||||
if err := c.releaseByHandle(handleID, blockName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c IPAMClient) releaseByHandle(handleID string, blockName string) error {
|
||||
for i := 0; i < datastoreRetries; i++ {
|
||||
block, err := c.queryBlock(blockName)
|
||||
if err != nil {
|
||||
if k8serrors.IsNotFound(err) {
|
||||
// Block doesn't exist, so all addresses are already
|
||||
// unallocated. This can happen when a handle is
|
||||
// overestimating the number of assigned addresses.
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
num := block.ReleaseByHandle(handleID)
|
||||
if num == 0 {
|
||||
// Block has no addresses with this handle, so
|
||||
// all addresses are already unallocated.
|
||||
return nil
|
||||
}
|
||||
|
||||
if block.Empty() {
|
||||
if err = c.DeleteBlock(block); err != nil {
|
||||
if k8serrors.IsConflict(err) {
|
||||
// Update conflict - retry.
|
||||
continue
|
||||
} else if !k8serrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Compare and swap the AllocationBlock using the original
|
||||
// KVPair read from before. No need to update the Value since we
|
||||
// have been directly manipulating the value referenced by the KVPair.
|
||||
_, err = c.client.NetworkV1alpha1().IPAMBlocks().Update(block)
|
||||
if err != nil {
|
||||
if k8serrors.IsConflict(err) {
|
||||
// Comparison failed - retry.
|
||||
continue
|
||||
} else {
|
||||
// Something else - return the error.
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.decrementHandle(handleID, block, num); err != nil {
|
||||
klog.Errorf("Failed to decrement handle %s, err=%s", handleID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return ErrMaxRetry
|
||||
}
|
||||
|
||||
func (c IPAMClient) incrementHandle(handleID string, block *v1alpha1.IPAMBlock, num int) error {
|
||||
for i := 0; i < datastoreRetries; i++ {
|
||||
create := false
|
||||
handle, err := c.queryHandle(handleID)
|
||||
if err != nil {
|
||||
if k8serrors.IsNotFound(err) {
|
||||
// Handle doesn't exist - create it.
|
||||
handle = &v1alpha1.IPAMHandle{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: handleID,
|
||||
},
|
||||
Spec: v1alpha1.IPAMHandleSpec{
|
||||
HandleID: handleID,
|
||||
Block: map[string]int{},
|
||||
},
|
||||
}
|
||||
create = true
|
||||
} else {
|
||||
// Unexpected error reading handle.
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Increment the handle for this block.
|
||||
handle.IncrementBlock(block, num)
|
||||
|
||||
if create {
|
||||
_, err = c.client.NetworkV1alpha1().IPAMHandles().Create(handle)
|
||||
} else {
|
||||
_, err = c.client.NetworkV1alpha1().IPAMHandles().Update(handle)
|
||||
}
|
||||
if err != nil {
|
||||
if k8serrors.IsAlreadyExists(err) || k8serrors.IsConflict(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrMaxRetry
|
||||
}
|
||||
|
||||
func (c IPAMClient) decrementHandle(handleID string, block *v1alpha1.IPAMBlock, num int) error {
|
||||
for i := 0; i < datastoreRetries; i++ {
|
||||
handle, err := c.queryHandle(handleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = handle.DecrementBlock(block, num)
|
||||
if err != nil {
|
||||
klog.Errorf("decrementHandle: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update / Delete as appropriate. Since we have been manipulating the
|
||||
// data in the KVPair, just pass this straight back to the client.
|
||||
if handle.Empty() {
|
||||
if err = c.deleteHandle(handle); err != nil {
|
||||
if k8serrors.IsConflict(err) {
|
||||
// Update conflict - retry.
|
||||
continue
|
||||
} else if !k8serrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err = c.client.NetworkV1alpha1().IPAMHandles().Update(handle); err != nil {
|
||||
if k8serrors.IsConflict(err) {
|
||||
// Update conflict - retry.
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrMaxRetry
|
||||
}
|
||||
|
||||
// GetUtilization returns IP utilization info for the specified pools, or for all pools.
|
||||
func (c IPAMClient) GetUtilization(args GetUtilizationArgs) ([]*PoolUtilization, error) {
|
||||
var usage []*PoolUtilization
|
||||
|
||||
// Read all pools.
|
||||
allPools, err := c.getAllPools()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Identify the ones we want and create a PoolUtilization for each of those.
|
||||
wantAllPools := len(args.Pools) == 0
|
||||
wantedPools := set.FromArray(args.Pools)
|
||||
for _, pool := range allPools {
|
||||
if wantAllPools || wantedPools.Contains(pool.Name) {
|
||||
cap := pool.NumAddresses()
|
||||
reserved := pool.NumReservedAddresses()
|
||||
usage = append(usage, &PoolUtilization{
|
||||
Name: pool.Name,
|
||||
Capacity: cap,
|
||||
Reserved: reserved,
|
||||
Allocate: 0,
|
||||
Unallocated: cap - reserved,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Find which pool this block belongs to.
|
||||
for _, poolUse := range usage {
|
||||
blocks, err := c.ListBlocks(poolUse.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(blocks) <= 0 {
|
||||
continue
|
||||
} else {
|
||||
poolUse.Reserved = 0
|
||||
poolUse.Allocate = 0
|
||||
}
|
||||
|
||||
for _, block := range blocks {
|
||||
poolUse.Allocate += block.NumAddresses() - block.NumFreeAddresses() - block.NumReservedAddresses()
|
||||
poolUse.Reserved += block.NumReservedAddresses()
|
||||
}
|
||||
|
||||
poolUse.Unallocated = poolUse.Capacity - poolUse.Allocate - poolUse.Reserved
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// findUnclaimedBlock finds a block cidr which does not yet exist within the given list of pools. The provided pools
|
||||
// should already be sanitized and only include existing, enabled pools. Note that the block may become claimed
|
||||
// between receiving the cidr from this function and attempting to claim the corresponding block as this function
|
||||
// does not reserve the returned IPNet.
|
||||
func (c IPAMClient) findUnclaimedBlock(pool *v1alpha1.IPPool) (*v1alpha1.IPAMBlock, error) {
|
||||
var result *v1alpha1.IPAMBlock
|
||||
|
||||
// List blocks up front to reduce number of queries.
|
||||
// We will try to write the block later to prevent races.
|
||||
existingBlocks, err := c.ListBlocks(pool.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/// Build a map for faster lookups.
|
||||
exists := map[string]bool{}
|
||||
for _, e := range existingBlocks {
|
||||
exists[fmt.Sprintf("%s", e.Spec.CIDR)] = true
|
||||
}
|
||||
|
||||
// Iterate through pools to find a new block.
|
||||
_, cidr, _ := cnet.ParseCIDR(pool.Spec.CIDR)
|
||||
poolType := pool.Spec.Type
|
||||
switch poolType {
|
||||
case v1alpha1.VLAN:
|
||||
if _, ok := exists[cidr.String()]; !ok {
|
||||
var reservedAttr *v1alpha1.ReservedAttr
|
||||
if pool.Spec.RangeStart != "" && pool.Spec.RangeEnd != "" {
|
||||
reservedAttr = &v1alpha1.ReservedAttr{
|
||||
StartOfBlock: pool.StartReservedAddressed(),
|
||||
EndOfBlock: pool.EndReservedAddressed(),
|
||||
Handle: v1alpha1.ReservedHandle,
|
||||
Note: v1alpha1.ReservedNote,
|
||||
}
|
||||
}
|
||||
result = v1alpha1.NewBlock(pool, *cidr, reservedAttr)
|
||||
}
|
||||
default:
|
||||
blocks := blockGenerator(pool)
|
||||
for subnet := blocks(); subnet != nil; subnet = blocks() {
|
||||
// Check if a block already exists for this subnet.
|
||||
if _, ok := exists[fmt.Sprintf("%s", subnet.String())]; !ok {
|
||||
result = v1alpha1.NewBlock(pool, *subnet, nil)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
return result, nil
|
||||
} else {
|
||||
return nil, ErrNoFreeBlocks
|
||||
}
|
||||
}
|
||||
|
||||
func (c IPAMClient) ListBlocks(pool string) ([]v1alpha1.IPAMBlock, error) {
|
||||
blocks, err := c.client.NetworkV1alpha1().IPAMBlocks().List(metav1.ListOptions{
|
||||
LabelSelector: labels.SelectorFromSet(labels.Set{
|
||||
v1alpha1.IPPoolNameLabel: pool,
|
||||
}).String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blocks.Items, nil
|
||||
}
|
||||
|
||||
// DeleteBlock deletes the given block.
|
||||
func (c IPAMClient) DeleteBlock(b *v1alpha1.IPAMBlock) error {
|
||||
if !b.IsDeleted() {
|
||||
b.MarkDeleted()
|
||||
_, err := c.client.NetworkV1alpha1().IPAMBlocks().Update(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.client.NetworkV1alpha1().IPAMBlocks().Delete(b.Name, nil)
|
||||
}
|
||||
|
||||
func (c IPAMClient) queryBlock(blockName string) (*v1alpha1.IPAMBlock, error) {
|
||||
block, err := c.client.NetworkV1alpha1().IPAMBlocks().Get(blockName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if block.IsDeleted() {
|
||||
err := c.DeleteBlock(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, k8serrors.NewNotFound(v1alpha1.Resource(v1alpha1.ResourcePluralIPAMBlock), blockName)
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// queryHandle gets a handle for the given handleID key.
|
||||
func (c IPAMClient) queryHandle(handleID string) (*v1alpha1.IPAMHandle, error) {
|
||||
handle, err := c.client.NetworkV1alpha1().IPAMHandles().Get(handleID, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if handle.IsDeleted() {
|
||||
err := c.deleteHandle(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, k8serrors.NewNotFound(v1alpha1.Resource(v1alpha1.ResourcePluralIPAMHandle), handleID)
|
||||
}
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// deleteHandle deletes the given handle.
|
||||
func (c IPAMClient) deleteHandle(h *v1alpha1.IPAMHandle) error {
|
||||
if !h.IsDeleted() {
|
||||
h.MarkDeleted()
|
||||
_, err := c.client.NetworkV1alpha1().IPAMHandles().Update(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.client.NetworkV1alpha1().IPAMHandles().Delete(h.Name, nil)
|
||||
}
|
||||
|
||||
// Generator to get list of block CIDRs which
|
||||
// fall within the given cidr. The passed in pool
|
||||
// must contain the passed in block cidr.
|
||||
// Returns nil when no more blocks can be generated.
|
||||
func blockGenerator(pool *v1alpha1.IPPool) func() *cnet.IPNet {
|
||||
tmp, cidr, _ := cnet.ParseCIDR(pool.Spec.CIDR)
|
||||
ip := *tmp
|
||||
|
||||
var blockMask net.IPMask
|
||||
if ip.Version() == 4 {
|
||||
blockMask = net.CIDRMask(pool.Spec.BlockSize, 32)
|
||||
} else {
|
||||
blockMask = net.CIDRMask(pool.Spec.BlockSize, 128)
|
||||
}
|
||||
|
||||
ones, size := blockMask.Size()
|
||||
blockSize := new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(size-ones)), nil)
|
||||
|
||||
return func() *cnet.IPNet {
|
||||
returnIP := ip
|
||||
|
||||
if cidr.Contains(ip.IP) {
|
||||
ipnet := net.IPNet{IP: returnIP.IP, Mask: blockMask}
|
||||
cidr := cnet.IPNet{IPNet: ipnet}
|
||||
ip = cnet.IncrementIP(ip, blockSize)
|
||||
return &cidr
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
139
pkg/simple/client/network/ippool/ipam/ipam_test.go
Normal file
139
pkg/simple/client/network/ippool/ipam/ipam_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
Copyright 2020 KubeSphere 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 ipam
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
||||
ksfake "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIPAM_blockGenerator(t *testing.T) {
|
||||
pool := &v1alpha1.IPPool{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "testippool",
|
||||
},
|
||||
Spec: v1alpha1.IPPoolSpec{
|
||||
Type: v1alpha1.VLAN,
|
||||
CIDR: "192.168.0.0/24",
|
||||
RangeEnd: "192.168.0.250",
|
||||
RangeStart: "192.168.0.10",
|
||||
BlockSize: 25,
|
||||
},
|
||||
}
|
||||
blocks := blockGenerator(pool)
|
||||
for subnet := blocks(); subnet != nil; subnet = blocks() {
|
||||
if subnet.String() != "192.168.0.0/25" && subnet.String() != "192.168.0.128/25" {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAMSuit(t *testing.T) {
|
||||
klog.InitFlags(nil)
|
||||
flag.Set("logtostderr", "true")
|
||||
flag.Set("v", "4")
|
||||
flag.Parse()
|
||||
klog.SetOutput(GinkgoWriter)
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "IPAM Test Suite")
|
||||
}
|
||||
|
||||
const (
|
||||
vlanIPPoolName = "testippool"
|
||||
)
|
||||
|
||||
var (
|
||||
vlanIPPool = &v1alpha1.IPPool{
|
||||
TypeMeta: v1.TypeMeta{},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: vlanIPPoolName,
|
||||
},
|
||||
Spec: v1alpha1.IPPoolSpec{
|
||||
Type: v1alpha1.VLAN,
|
||||
CIDR: "192.168.0.0/24",
|
||||
},
|
||||
Status: v1alpha1.IPPoolStatus{},
|
||||
}
|
||||
)
|
||||
|
||||
func newVLANIPAMClient() IPAMClient {
|
||||
vlanIPPool.Labels = map[string]string{
|
||||
v1alpha1.IPPoolTypeLabel: vlanIPPool.Spec.Type,
|
||||
v1alpha1.IPPoolNameLabel: vlanIPPool.Name,
|
||||
v1alpha1.IPPoolIDLabel: fmt.Sprintf("%d", vlanIPPool.ID()),
|
||||
}
|
||||
return IPAMClient{
|
||||
typeStr: v1alpha1.VLAN,
|
||||
client: ksfake.NewSimpleClientset(vlanIPPool),
|
||||
}
|
||||
}
|
||||
|
||||
func TestIpamClient_GetAllPools(t *testing.T) {
|
||||
c := newVLANIPAMClient()
|
||||
pools, _ := c.getAllPools()
|
||||
if len(pools) != 1 {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
var _ = Describe("test vlan ippool", func() {
|
||||
It("test get all vlan ippool", func() {
|
||||
c := newVLANIPAMClient()
|
||||
pools, _ := c.getAllPools()
|
||||
Expect(len(pools)).To(Equal(1))
|
||||
})
|
||||
|
||||
It("test get pool utilization", func() {
|
||||
c := newVLANIPAMClient()
|
||||
stats, _ := c.GetUtilization(GetUtilizationArgs{Pools: []string{vlanIPPoolName}})
|
||||
Expect(len(stats)).To(Equal(1))
|
||||
Expect(stats[0].Unallocated).To(Equal(256))
|
||||
})
|
||||
|
||||
It("test auto assign/unassign ip", func() {
|
||||
c := newVLANIPAMClient()
|
||||
result, _ := c.AutoAssign(AutoAssignArgs{
|
||||
HandleID: "testhandle",
|
||||
Pool: vlanIPPoolName,
|
||||
})
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
stats, _ := c.GetUtilization(GetUtilizationArgs{Pools: []string{vlanIPPoolName}})
|
||||
Expect(stats[0].Unallocated).To(Equal(255))
|
||||
|
||||
blocks, _ := c.client.NetworkV1alpha1().IPAMBlocks().List(v1.ListOptions{})
|
||||
Expect(len(blocks.Items)).To(Equal(1))
|
||||
Expect(blocks.Items[0].BlockName()).To(Equal(fmt.Sprintf("%d-%s", vlanIPPool.ID(), "192-168-0-0-24")))
|
||||
|
||||
handles, _ := c.client.NetworkV1alpha1().IPAMHandles().List(v1.ListOptions{})
|
||||
Expect(len(handles.Items)).To(Equal(1))
|
||||
Expect(handles.Items[0].Name).To(Equal("testhandle"))
|
||||
|
||||
Expect(c.ReleaseByHandle("testhandle")).ShouldNot(HaveOccurred())
|
||||
blocks, _ = c.client.NetworkV1alpha1().IPAMBlocks().List(v1.ListOptions{})
|
||||
Expect(len(blocks.Items)).To(Equal(0))
|
||||
|
||||
handles, _ = c.client.NetworkV1alpha1().IPAMHandles().List(v1.ListOptions{})
|
||||
Expect(len(handles.Items)).To(Equal(0))
|
||||
})
|
||||
})
|
||||
51
pkg/simple/client/network/ippool/ipam/ipam_types.go
Normal file
51
pkg/simple/client/network/ippool/ipam/ipam_types.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2020 KubeSphere 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 ipam
|
||||
|
||||
// AutoAssignArgs defines the set of arguments for assigning one or more
|
||||
// IP addresses.
|
||||
type AutoAssignArgs struct {
|
||||
HandleID string
|
||||
|
||||
// A key/value mapping of metadata to store with the allocations.
|
||||
Attrs map[string]string
|
||||
|
||||
Pool string
|
||||
}
|
||||
|
||||
// GetUtilizationArgs defines the set of arguments for requesting IP utilization.
|
||||
type GetUtilizationArgs struct {
|
||||
// If specified, the pools whose utilization should be reported. Each string here
|
||||
// can be a pool name or CIDR. If not specified, this defaults to all pools.
|
||||
Pools []string
|
||||
}
|
||||
|
||||
// PoolUtilization reports IP utilization for a single IP pool.
|
||||
type PoolUtilization struct {
|
||||
// This pool's name.
|
||||
Name string
|
||||
|
||||
// Number of possible IPs in this block.
|
||||
Capacity int
|
||||
|
||||
// Number of available IPs in this block.
|
||||
Unallocated int
|
||||
|
||||
Allocate int
|
||||
|
||||
Reserved int
|
||||
}
|
||||
Reference in New Issue
Block a user