cni/network/invoker_azure.go (180 lines of code) (raw):
package network
import (
"fmt"
"net"
"os"
"runtime/debug"
"strings"
"github.com/Azure/azure-container-networking/cni"
"github.com/Azure/azure-container-networking/cni/log"
"github.com/Azure/azure-container-networking/cns"
"github.com/Azure/azure-container-networking/common"
"github.com/Azure/azure-container-networking/ipam"
"github.com/Azure/azure-container-networking/network"
"github.com/Azure/azure-container-networking/platform"
cniSkel "github.com/containernetworking/cni/pkg/skel"
cniTypes "github.com/containernetworking/cni/pkg/types"
cniTypesCurr "github.com/containernetworking/cni/pkg/types/100"
"go.uber.org/zap"
)
var logger = log.CNILogger.With(zap.String("component", "cni-net"))
const (
bytesSize4 = 4
bytesSize16 = 16
)
type AzureIPAMInvoker struct {
plugin delegatePlugin
nwInfo *network.EndpointInfo
}
type delegatePlugin interface {
DelegateAdd(pluginName string, nwCfg *cni.NetworkConfig) (*cniTypesCurr.Result, error)
DelegateDel(pluginName string, nwCfg *cni.NetworkConfig) error
Errorf(format string, args ...interface{}) *cniTypes.Error
}
// Create an IPAM instance every time a CNI action is called.
func NewAzureIpamInvoker(plugin *NetPlugin, nwInfo *network.EndpointInfo) *AzureIPAMInvoker {
return &AzureIPAMInvoker{
plugin: plugin,
nwInfo: nwInfo,
}
}
func (invoker *AzureIPAMInvoker) Add(addConfig IPAMAddConfig) (IPAMAddResult, error) {
addResult := IPAMAddResult{interfaceInfo: make(map[string]network.InterfaceInfo)}
if addConfig.nwCfg == nil {
return addResult, invoker.plugin.Errorf("nil nwCfg passed to CNI ADD, stack: %+v", string(debug.Stack()))
}
if len(invoker.nwInfo.Subnets) > 0 {
addConfig.nwCfg.IPAM.Subnet = invoker.nwInfo.Subnets[0].Prefix.String()
}
// Call into IPAM plugin to allocate an address pool for the network.
result, err := invoker.plugin.DelegateAdd(addConfig.nwCfg.IPAM.Type, addConfig.nwCfg)
if err != nil && strings.Contains(err.Error(), ipam.ErrNoAvailableAddressPools.Error()) {
invoker.deleteIpamState()
logger.Info("Retry pool allocation after deleting IPAM state")
result, err = invoker.plugin.DelegateAdd(addConfig.nwCfg.IPAM.Type, addConfig.nwCfg)
}
if err != nil {
err = invoker.plugin.Errorf("Failed to allocate pool: %v", err)
return addResult, err
}
defer func() {
if err != nil {
if len(addResult.interfaceInfo) > 0 && len(addResult.interfaceInfo[invoker.getInterfaceInfoKey(cns.InfraNIC)].IPConfigs) > 0 {
if er := invoker.Delete(&addResult.interfaceInfo[invoker.getInterfaceInfoKey(cns.InfraNIC)].IPConfigs[0].Address, addConfig.nwCfg, nil, addConfig.options); er != nil {
err = invoker.plugin.Errorf("Failed to clean up IP's during Delete with error %v, after Add failed with error %w", er, err)
}
} else {
err = fmt.Errorf("No IP's to delete on error: %v", err)
}
}
}()
if addConfig.nwCfg.IPV6Mode != "" {
nwCfg6 := *addConfig.nwCfg
nwCfg6.IPAM.Environment = common.OptEnvironmentIPv6NodeIpam
nwCfg6.IPAM.Type = ipamV6
if len(invoker.nwInfo.Subnets) > 1 {
// ipv6 is the second subnet of the slice
nwCfg6.IPAM.Subnet = invoker.nwInfo.Subnets[1].Prefix.String()
}
var ipv6Result *cniTypesCurr.Result
ipv6Result, err = invoker.plugin.DelegateAdd(nwCfg6.IPAM.Type, &nwCfg6)
if err != nil {
err = invoker.plugin.Errorf("Failed to allocate v6 pool: %v", err)
} else {
result.IPs = append(result.IPs, ipv6Result.IPs...)
result.Routes = append(result.Routes, ipv6Result.Routes...)
addResult.ipv6Enabled = true
}
}
ipconfigs := make([]*network.IPConfig, len(result.IPs))
for i, ipconfig := range result.IPs {
ipconfigs[i] = &network.IPConfig{Address: ipconfig.Address, Gateway: ipconfig.Gateway}
}
routes := make([]network.RouteInfo, len(result.Routes))
for i, route := range result.Routes {
routes[i] = network.RouteInfo{Dst: route.Dst, Gw: route.GW}
}
// TODO: changed how host subnet prefix populated (check)
hostSubnetPrefix := net.IPNet{}
if len(result.IPs) > 0 {
hostSubnetPrefix = result.IPs[0].Address
}
addResult.interfaceInfo[invoker.getInterfaceInfoKey(cns.InfraNIC)] = network.InterfaceInfo{
IPConfigs: ipconfigs,
Routes: routes,
DNS: network.DNSInfo{
Suffix: result.DNS.Domain,
Servers: result.DNS.Nameservers,
},
NICType: cns.InfraNIC,
HostSubnetPrefix: hostSubnetPrefix,
}
return addResult, err
}
func (invoker *AzureIPAMInvoker) deleteIpamState() {
cniStateExists, err := platform.CheckIfFileExists(platform.CNIStateFilePath)
if err != nil {
logger.Error("Error checking CNI state exist", zap.Error(err))
return
}
if cniStateExists {
return
}
ipamStateExists, err := platform.CheckIfFileExists(platform.CNIIpamStatePath)
if err != nil {
logger.Error("Error checking IPAM state exist", zap.Error(err))
return
}
if ipamStateExists {
logger.Info("Deleting IPAM state file")
err = os.Remove(platform.CNIIpamStatePath)
if err != nil {
logger.Error("Error deleting state file", zap.Error(err))
return
}
}
}
func (invoker *AzureIPAMInvoker) Delete(address *net.IPNet, nwCfg *cni.NetworkConfig, _ *cniSkel.CmdArgs, options map[string]interface{}) error { //nolint
if nwCfg == nil {
return invoker.plugin.Errorf("nil nwCfg passed to CNI ADD, stack: %+v", string(debug.Stack()))
}
if len(invoker.nwInfo.Subnets) > 0 {
nwCfg.IPAM.Subnet = invoker.nwInfo.Subnets[0].Prefix.String()
}
if address == nil {
if err := invoker.plugin.DelegateDel(nwCfg.IPAM.Type, nwCfg); err != nil {
return invoker.plugin.Errorf("Attempted to release address with error: %v", err)
}
} else if len(address.IP.To4()) == bytesSize4 { //nolint:gocritic
nwCfg.IPAM.Address = address.IP.String()
logger.Info("Releasing ipv4",
zap.String("address", nwCfg.IPAM.Address),
zap.String("pool", nwCfg.IPAM.Subnet))
if err := invoker.plugin.DelegateDel(nwCfg.IPAM.Type, nwCfg); err != nil {
logger.Error("Failed to release ipv4 address", zap.Error(err))
return invoker.plugin.Errorf("Failed to release ipv4 address: %v", err)
}
} else if len(address.IP.To16()) == bytesSize16 {
nwCfgIpv6 := *nwCfg
nwCfgIpv6.IPAM.Environment = common.OptEnvironmentIPv6NodeIpam
nwCfgIpv6.IPAM.Type = ipamV6
nwCfgIpv6.IPAM.Address = address.IP.String()
if len(invoker.nwInfo.Subnets) > 1 {
for _, subnet := range invoker.nwInfo.Subnets {
if subnet.Prefix.IP.To4() == nil {
nwCfgIpv6.IPAM.Subnet = subnet.Prefix.String()
break
}
}
}
logger.Info("Releasing ipv6",
zap.String("address", nwCfgIpv6.IPAM.Address),
zap.String("pool", nwCfgIpv6.IPAM.Subnet))
if err := invoker.plugin.DelegateDel(nwCfgIpv6.IPAM.Type, &nwCfgIpv6); err != nil {
logger.Error("Failed to release ipv6 address", zap.Error(err))
return invoker.plugin.Errorf("Failed to release ipv6 address: %v", err)
}
} else {
return invoker.plugin.Errorf("Address is incorrect, not valid IPv4 or IPv6, stack: %+v", string(debug.Stack()))
}
return nil
}
func (invoker *AzureIPAMInvoker) getInterfaceInfoKey(nicType cns.NICType) string {
return string(nicType)
}