* feat: add cni conflist generator for v4 overlay scenario

* feat: use atomic fs operations

* fix: use same directory as temp dir since /tmp is a tmpfs
This commit is contained in:
Matthew Long 2022-11-28 20:56:08 -08:00 коммит произвёл GitHub
Родитель 8cc8e7f1ff
Коммит 7b91752d10
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
26 изменённых файлов: 483 добавлений и 114 удалений

Просмотреть файл

@ -18,8 +18,8 @@ const (
func CallPlugin(plugin PluginApi, cmd string, args *cniSkel.CmdArgs, nwCfg *NetworkConfig) (*cniTypes.Result, error) {
var err error
savedType := nwCfg.Ipam.Type
nwCfg.Ipam.Type = Internal
savedType := nwCfg.IPAM.Type
nwCfg.IPAM.Type = Internal
args.StdinData = nwCfg.Serialize()
// Call the plugin's internal interface.
@ -29,7 +29,7 @@ func CallPlugin(plugin PluginApi, cmd string, args *cniSkel.CmdArgs, nwCfg *Netw
err = plugin.Delete(args)
}
nwCfg.Ipam.Type = savedType
nwCfg.IPAM.Type = savedType
if err != nil {
return nil, err

Просмотреть файл

@ -101,14 +101,14 @@ func (plugin *ipamPlugin) Configure(stdinData []byte) (*cni.NetworkConfig, error
// Apply IPAM configuration.
// Set deployment environment.
if nwCfg.Ipam.Environment == "" {
nwCfg.Ipam.Environment = common.OptEnvironmentAzure
if nwCfg.IPAM.Environment == "" {
nwCfg.IPAM.Environment = common.OptEnvironmentAzure
}
plugin.SetOption(common.OptEnvironment, nwCfg.Ipam.Environment)
plugin.SetOption(common.OptEnvironment, nwCfg.IPAM.Environment)
// Set query interval.
if nwCfg.Ipam.QueryInterval != "" {
i, _ := strconv.Atoi(nwCfg.Ipam.QueryInterval)
if nwCfg.IPAM.QueryInterval != "" {
i, _ := strconv.Atoi(nwCfg.IPAM.QueryInterval)
plugin.SetOption(common.OptIpamQueryInterval, i)
}
@ -118,8 +118,8 @@ func (plugin *ipamPlugin) Configure(stdinData []byte) (*cni.NetworkConfig, error
}
// Set default address space if not specified.
if nwCfg.Ipam.AddrSpace == "" {
nwCfg.Ipam.AddrSpace = ipam.LocalDefaultAddressSpaceId
if nwCfg.IPAM.AddrSpace == "" {
nwCfg.IPAM.AddrSpace = ipam.LocalDefaultAddressSpaceId
}
return nwCfg, nil
@ -152,12 +152,12 @@ func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
options[ipam.OptAddressID] = args.ContainerID
// Check if an address pool is specified.
if nwCfg.Ipam.Subnet == "" {
if nwCfg.IPAM.Subnet == "" {
var poolID string
var subnet string
isIpv6 := false
if nwCfg.Ipam.Type == ipamV6 {
if nwCfg.IPAM.Type == ipamV6 {
isIpv6 = true
}
@ -165,7 +165,7 @@ func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
options[ipam.OptInterfaceName] = nwCfg.Master
// Allocate an address pool.
poolID, subnet, err = plugin.am.RequestPool(nwCfg.Ipam.AddrSpace, "", "", options, isIpv6)
poolID, subnet, err = plugin.am.RequestPool(nwCfg.IPAM.AddrSpace, "", "", options, isIpv6)
if err != nil {
err = plugin.Errorf("Failed to allocate pool: %v", err)
return err
@ -175,16 +175,16 @@ func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
defer func() {
if err != nil && poolID != "" {
log.Printf("[cni-ipam] Releasing pool %v.", poolID)
plugin.am.ReleasePool(nwCfg.Ipam.AddrSpace, poolID)
_ = plugin.am.ReleasePool(nwCfg.IPAM.AddrSpace, poolID)
}
}()
nwCfg.Ipam.Subnet = subnet
nwCfg.IPAM.Subnet = subnet
log.Printf("[cni-ipam] Allocated address poolID %v with subnet %v.", poolID, subnet)
}
// Allocate an address for the endpoint.
address, err := plugin.am.RequestAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address, options)
address, err := plugin.am.RequestAddress(nwCfg.IPAM.AddrSpace, nwCfg.IPAM.Subnet, nwCfg.IPAM.Address, options)
if err != nil {
err = plugin.Errorf("Failed to allocate address: %v", err)
return err
@ -194,7 +194,7 @@ func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
defer func() {
if err != nil && address != "" {
log.Printf("[cni-ipam] Releasing address %v.", address)
plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, address, options)
_ = plugin.am.ReleaseAddress(nwCfg.IPAM.AddrSpace, nwCfg.IPAM.Subnet, address, options)
}
}()
@ -208,7 +208,7 @@ func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
}
// Query pool information for gateways and DNS servers.
apInfo, err := plugin.am.GetPoolInfo(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet)
apInfo, err := plugin.am.GetPoolInfo(nwCfg.IPAM.AddrSpace, nwCfg.IPAM.Subnet)
if err != nil {
err = plugin.Errorf("Failed to get pool information: %v", err)
return err
@ -243,7 +243,7 @@ func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
}
// Output the result.
if nwCfg.Ipam.Type == cni.Internal {
if nwCfg.IPAM.Type == cni.Internal {
// Called via the internal interface. Pass output back in args.
args.StdinData, _ = json.Marshal(res)
} else {
@ -279,7 +279,7 @@ func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error {
options := make(map[string]string)
options[ipam.OptAddressID] = args.ContainerID
err = plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address, options)
err = plugin.am.ReleaseAddress(nwCfg.IPAM.AddrSpace, nwCfg.IPAM.Subnet, nwCfg.IPAM.Address, options)
if err != nil {
err = plugin.Errorf("Failed to release address: %v", err)

Просмотреть файл

@ -40,43 +40,45 @@ type RuntimeDNSConfig struct {
Options []string `json:"options,omitempty"`
}
type IPAM struct {
Mode string `json:"mode,omitempty"`
Type string `json:"type"`
Environment string `json:"environment,omitempty"`
AddrSpace string `json:"addressSpace,omitempty"`
Subnet string `json:"subnet,omitempty"`
Address string `json:"ipAddress,omitempty"`
QueryInterval string `json:"queryInterval,omitempty"`
}
// NetworkConfig represents Azure CNI plugin network configuration.
type NetworkConfig struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Mode string `json:"mode,omitempty"`
Master string `json:"master,omitempty"`
AdapterName string `json:"adapterName,omitempty"`
Bridge string `json:"bridge,omitempty"`
LogLevel string `json:"logLevel,omitempty"`
LogTarget string `json:"logTarget,omitempty"`
InfraVnetAddressSpace string `json:"infraVnetAddressSpace,omitempty"`
IPV6Mode string `json:"ipv6Mode,omitempty"`
ServiceCidrs string `json:"serviceCidrs,omitempty"`
VnetCidrs string `json:"vnetCidrs,omitempty"`
PodNamespaceForDualNetwork []string `json:"podNamespaceForDualNetwork,omitempty"`
IPsToRouteViaHost []string `json:"ipsToRouteViaHost,omitempty"`
MultiTenancy bool `json:"multiTenancy,omitempty"`
EnableSnatOnHost bool `json:"enableSnatOnHost,omitempty"`
EnableExactMatchForPodName bool `json:"enableExactMatchForPodName,omitempty"`
DisableHairpinOnHostInterface bool `json:"disableHairpinOnHostInterface,omitempty"`
DisableIPTableLock bool `json:"disableIPTableLock,omitempty"`
CNSUrl string `json:"cnsurl,omitempty"`
ExecutionMode string `json:"executionMode,omitempty"`
Ipam struct {
Mode string `json:"mode,omitempty"`
Type string `json:"type"`
Environment string `json:"environment,omitempty"`
AddrSpace string `json:"addressSpace,omitempty"`
Subnet string `json:"subnet,omitempty"`
Address string `json:"ipAddress,omitempty"`
QueryInterval string `json:"queryInterval,omitempty"`
} `json:"ipam,omitempty"`
DNS cniTypes.DNS `json:"dns,omitempty"`
RuntimeConfig RuntimeConfig `json:"runtimeConfig,omitempty"`
WindowsSettings WindowsSettings `json:"windowsSettings,omitempty"`
AdditionalArgs []KVPair `json:"AdditionalArgs,omitempty"`
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Mode string `json:"mode,omitempty"`
Master string `json:"master,omitempty"`
AdapterName string `json:"adapterName,omitempty"`
Bridge string `json:"bridge,omitempty"`
LogLevel string `json:"logLevel,omitempty"`
LogTarget string `json:"logTarget,omitempty"`
InfraVnetAddressSpace string `json:"infraVnetAddressSpace,omitempty"`
IPV6Mode string `json:"ipv6Mode,omitempty"`
ServiceCidrs string `json:"serviceCidrs,omitempty"`
VnetCidrs string `json:"vnetCidrs,omitempty"`
PodNamespaceForDualNetwork []string `json:"podNamespaceForDualNetwork,omitempty"`
IPsToRouteViaHost []string `json:"ipsToRouteViaHost,omitempty"`
MultiTenancy bool `json:"multiTenancy,omitempty"`
EnableSnatOnHost bool `json:"enableSnatOnHost,omitempty"`
EnableExactMatchForPodName bool `json:"enableExactMatchForPodName,omitempty"`
DisableHairpinOnHostInterface bool `json:"disableHairpinOnHostInterface,omitempty"`
DisableIPTableLock bool `json:"disableIPTableLock,omitempty"`
CNSUrl string `json:"cnsurl,omitempty"`
ExecutionMode string `json:"executionMode,omitempty"`
IPAM IPAM `json:"ipam,omitempty"`
DNS cniTypes.DNS `json:"dns,omitempty"`
RuntimeConfig RuntimeConfig `json:"runtimeConfig,omitempty"`
WindowsSettings WindowsSettings `json:"windowsSettings,omitempty"`
AdditionalArgs []KVPair `json:"AdditionalArgs,omitempty"`
}
type WindowsSettings struct {

Просмотреть файл

@ -48,11 +48,11 @@ func (invoker *AzureIPAMInvoker) Add(addConfig IPAMAddConfig) (IPAMAddResult, er
}
if len(invoker.nwInfo.Subnets) > 0 {
addConfig.nwCfg.Ipam.Subnet = invoker.nwInfo.Subnets[0].Prefix.String()
addConfig.nwCfg.IPAM.Subnet = invoker.nwInfo.Subnets[0].Prefix.String()
}
// Call into IPAM plugin to allocate an address pool for the network.
addResult.ipv4Result, err = invoker.plugin.DelegateAdd(addConfig.nwCfg.Ipam.Type, addConfig.nwCfg)
addResult.ipv4Result, err = invoker.plugin.DelegateAdd(addConfig.nwCfg.IPAM.Type, addConfig.nwCfg)
if err != nil && strings.Contains(err.Error(), ipam.ErrNoAvailableAddressPools.Error()) {
invoker.deleteIpamState()
@ -76,15 +76,15 @@ func (invoker *AzureIPAMInvoker) Add(addConfig IPAMAddConfig) (IPAMAddResult, er
if addConfig.nwCfg.IPV6Mode != "" {
nwCfg6 := *addConfig.nwCfg
nwCfg6.Ipam.Environment = common.OptEnvironmentIPv6NodeIpam
nwCfg6.Ipam.Type = ipamV6
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()
nwCfg6.IPAM.Subnet = invoker.nwInfo.Subnets[1].Prefix.String()
}
addResult.ipv6Result, err = invoker.plugin.DelegateAdd(nwCfg6.Ipam.Type, &nwCfg6)
addResult.ipv6Result, err = invoker.plugin.DelegateAdd(nwCfg6.IPAM.Type, &nwCfg6)
if err != nil {
err = invoker.plugin.Errorf("Failed to allocate v6 pool: %v", err)
}
@ -128,33 +128,33 @@ func (invoker *AzureIPAMInvoker) Delete(address *net.IPNet, nwCfg *cni.NetworkCo
}
if len(invoker.nwInfo.Subnets) > 0 {
nwCfg.Ipam.Subnet = invoker.nwInfo.Subnets[0].Prefix.String()
nwCfg.IPAM.Subnet = invoker.nwInfo.Subnets[0].Prefix.String()
}
if address == nil {
if err := invoker.plugin.DelegateDel(nwCfg.Ipam.Type, nwCfg); err != 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()) == 4 {
nwCfg.Ipam.Address = address.IP.String()
nwCfg.IPAM.Address = address.IP.String()
log.Printf("Releasing ipv4 address :%s pool: %s",
nwCfg.Ipam.Address, nwCfg.Ipam.Subnet)
if err := invoker.plugin.DelegateDel(nwCfg.Ipam.Type, nwCfg); err != nil {
nwCfg.IPAM.Address, nwCfg.IPAM.Subnet)
if err := invoker.plugin.DelegateDel(nwCfg.IPAM.Type, nwCfg); err != nil {
log.Printf("Failed to release ipv4 address: %v", err)
return invoker.plugin.Errorf("Failed to release ipv4 address: %v", err)
}
} else if len(address.IP.To16()) == 16 {
nwCfgIpv6 := *nwCfg
nwCfgIpv6.Ipam.Environment = common.OptEnvironmentIPv6NodeIpam
nwCfgIpv6.Ipam.Type = ipamV6
nwCfgIpv6.Ipam.Address = address.IP.String()
nwCfgIpv6.IPAM.Environment = common.OptEnvironmentIPv6NodeIpam
nwCfgIpv6.IPAM.Type = ipamV6
nwCfgIpv6.IPAM.Address = address.IP.String()
if len(invoker.nwInfo.Subnets) > 1 {
nwCfgIpv6.Ipam.Subnet = invoker.nwInfo.Subnets[1].Prefix.String()
nwCfgIpv6.IPAM.Subnet = invoker.nwInfo.Subnets[1].Prefix.String()
}
log.Printf("Releasing ipv6 address :%s pool: %s",
nwCfgIpv6.Ipam.Address, nwCfgIpv6.Ipam.Subnet)
if err := invoker.plugin.DelegateDel(nwCfgIpv6.Ipam.Type, &nwCfgIpv6); err != nil {
nwCfgIpv6.IPAM.Address, nwCfgIpv6.IPAM.Subnet)
if err := invoker.plugin.DelegateDel(nwCfgIpv6.IPAM.Type, &nwCfgIpv6); err != nil {
log.Printf("Failed to release ipv6 address: %v", err)
return invoker.plugin.Errorf("Failed to release ipv6 address: %v", err)
}

Просмотреть файл

@ -94,17 +94,6 @@ func getResult(ip string) []*cniTypesCurr.Result {
return res
}
// used in the tests below, unused ignores tags
type ipamStruct struct { //nolint:unused
Mode string `json:"mode,omitempty"`
Type string `json:"type"`
Environment string `json:"environment,omitempty"`
AddrSpace string `json:"addressSpace,omitempty"`
Subnet string `json:"subnet,omitempty"`
Address string `json:"ipAddress,omitempty"`
QueryInterval string `json:"queryInterval,omitempty"`
}
func getNwInfo(subnetv4, subnetv6 string) *network.NetworkInfo {
nwinfo := &network.NetworkInfo{}
if subnetv4 != "" {
@ -268,7 +257,7 @@ func TestAzureIPAMInvoker_Delete(t *testing.T) {
args: args{
address: getCIDRNotationForAddress("10.0.0.4/24"),
nwCfg: &cni.NetworkConfig{
Ipam: ipamStruct{
IPAM: cni.IPAM{
Address: "10.0.0.4",
},
},
@ -285,7 +274,7 @@ func TestAzureIPAMInvoker_Delete(t *testing.T) {
args: args{
address: getCIDRNotationForAddress("2001:db8:abcd:0015::0/64"),
nwCfg: &cni.NetworkConfig{
Ipam: ipamStruct{
IPAM: cni.IPAM{
Address: "2001:db8:abcd:0015::0/64",
},
},
@ -304,7 +293,7 @@ func TestAzureIPAMInvoker_Delete(t *testing.T) {
args: args{
address: nil,
nwCfg: &cni.NetworkConfig{
Ipam: ipamStruct{
IPAM: cni.IPAM{
Address: "2001:db8:abcd:0015::0/64",
},
},
@ -324,7 +313,7 @@ func TestAzureIPAMInvoker_Delete(t *testing.T) {
args: args{
address: getCIDRNotationForAddress("10.0.0.4/24"),
nwCfg: &cni.NetworkConfig{
Ipam: ipamStruct{
IPAM: cni.IPAM{
Address: "10.0.0.4/24",
},
},
@ -344,7 +333,7 @@ func TestAzureIPAMInvoker_Delete(t *testing.T) {
args: args{
address: getCIDRNotationForAddress("2001:db8:abcd:0015::0/64"),
nwCfg: &cni.NetworkConfig{
Ipam: ipamStruct{
IPAM: cni.IPAM{
Address: "10.0.0.4/24",
},
},

Просмотреть файл

@ -279,9 +279,9 @@ func getInfraVnetIP(
) (*cniTypesCurr.Result, error) {
if enableInfraVnet {
_, ipNet, _ := net.ParseCIDR(infraSubnet)
nwCfg.Ipam.Subnet = ipNet.String()
nwCfg.IPAM.Subnet = ipNet.String()
log.Printf("call ipam to allocate ip from subnet %v", nwCfg.Ipam.Subnet)
log.Printf("call ipam to allocate ip from subnet %v", nwCfg.IPAM.Subnet)
ipamAddOpt := IPAMAddConfig{nwCfg: nwCfg, options: make(map[string]interface{})}
ipamAddResult, err := plugin.ipamInvoker.Add(ipamAddOpt)
if err != nil {

Просмотреть файл

@ -196,7 +196,7 @@ func TestCleanupMultitenancyResources(t *testing.T) {
nwCfg: &cni.NetworkConfig{
MultiTenancy: true,
EnableSnatOnHost: false,
Ipam: ipamStruct{},
IPAM: cni.IPAM{},
},
infraIPNet: &cniTypesCurr.Result{},
plugin: &NetPlugin{
@ -244,7 +244,7 @@ func TestGetMultiTenancyCNIResult(t *testing.T) {
EnableSnatOnHost: true,
EnableExactMatchForPodName: true,
InfraVnetAddressSpace: "10.0.0.0/16",
Ipam: ipamStruct{Type: "azure-vnet-ipam"},
IPAM: cni.IPAM{Type: "azure-vnet-ipam"},
},
plugin: &NetPlugin{
ipamInvoker: NewMockIpamInvoker(false, false, false),

Просмотреть файл

@ -36,7 +36,7 @@ import (
const (
dockerNetworkOption = "com.docker.network.generic"
opModeTransparent = "transparent"
OpModeTransparent = "transparent"
// Supported IP version. Currently support only IPv4
ipamV6 = "azure-vnet-ipamv6"
defaultRequestTimeout = 15 * time.Second
@ -479,7 +479,7 @@ func (plugin *NetPlugin) Add(args *cniSkel.CmdArgs) error {
*/
if nwInfoErr == nil {
log.Printf("[cni-net] Found network %v with subnet %v.", networkID, nwInfo.Subnets[0].Prefix.String())
nwInfo.IPAMType = nwCfg.Ipam.Type
nwInfo.IPAMType = nwCfg.IPAM.Type
options = nwInfo.Options
var resultSecondAdd *cniTypesCurr.Result
@ -497,9 +497,9 @@ func (plugin *NetPlugin) Add(args *cniSkel.CmdArgs) error {
// Initialize azureipam/cns ipam
if plugin.ipamInvoker == nil {
switch nwCfg.Ipam.Type {
switch nwCfg.IPAM.Type {
case network.AzureCNS:
plugin.ipamInvoker = NewCNSInvoker(k8sPodName, k8sNamespace, cnsClient, util.ExecutionMode(nwCfg.ExecutionMode), util.IpamMode(nwCfg.Ipam.Mode))
plugin.ipamInvoker = NewCNSInvoker(k8sPodName, k8sNamespace, cnsClient, util.ExecutionMode(nwCfg.ExecutionMode), util.IpamMode(nwCfg.IPAM.Mode))
default:
plugin.ipamInvoker = NewAzureIpamInvoker(plugin, &nwInfo)
@ -592,7 +592,7 @@ func (plugin *NetPlugin) createNetworkInternal(
) (network.NetworkInfo, error) {
nwInfo := network.NetworkInfo{}
ipamAddResult.hostSubnetPrefix.IP = ipamAddResult.hostSubnetPrefix.IP.Mask(ipamAddResult.hostSubnetPrefix.Mask)
ipamAddConfig.nwCfg.Ipam.Subnet = ipamAddResult.hostSubnetPrefix.String()
ipamAddConfig.nwCfg.IPAM.Subnet = ipamAddResult.hostSubnetPrefix.String()
// Find the master interface.
masterIfName := plugin.findMasterInterface(ipamAddConfig.nwCfg, &ipamAddResult.hostSubnetPrefix)
if masterIfName == "" {
@ -643,7 +643,7 @@ func (plugin *NetPlugin) createNetworkInternal(
Options: ipamAddConfig.options,
DisableHairpinOnHostInterface: ipamAddConfig.nwCfg.DisableHairpinOnHostInterface,
IPV6Mode: ipamAddConfig.nwCfg.IPV6Mode,
IPAMType: ipamAddConfig.nwCfg.Ipam.Type,
IPAMType: ipamAddConfig.nwCfg.IPAM.Type,
ServiceCidrs: ipamAddConfig.nwCfg.ServiceCidrs,
}
@ -698,7 +698,7 @@ func (plugin *NetPlugin) createEndpointInternal(opt *createEndpointInternalOpt)
opt.policies = append(opt.policies, endpointPolicies...)
vethName := fmt.Sprintf("%s.%s", opt.k8sNamespace, opt.k8sPodName)
if opt.nwCfg.Mode != opModeTransparent {
if opt.nwCfg.Mode != OpModeTransparent {
// this mechanism of using only namespace and name is not unique for different incarnations of POD/container.
// IT will result in unpredictable behavior if API server decides to
// reorder DELETE and ADD call for new incarnation of same POD.
@ -927,14 +927,14 @@ func (plugin *NetPlugin) Delete(args *cniSkel.CmdArgs) error {
}
if plugin.ipamInvoker == nil {
switch nwCfg.Ipam.Type {
switch nwCfg.IPAM.Type {
case network.AzureCNS:
cnsClient, cnsErr := cnscli.New("", defaultRequestTimeout)
if cnsErr != nil {
log.Printf("[cni-net] failed to create cns client:%v", cnsErr)
return errors.Wrap(cnsErr, "failed to create cns client")
}
plugin.ipamInvoker = NewCNSInvoker(k8sPodName, k8sNamespace, cnsClient, util.ExecutionMode(nwCfg.ExecutionMode), util.IpamMode(nwCfg.Ipam.Mode))
plugin.ipamInvoker = NewCNSInvoker(k8sPodName, k8sNamespace, cnsClient, util.ExecutionMode(nwCfg.ExecutionMode), util.IpamMode(nwCfg.IPAM.Mode))
default:
plugin.ipamInvoker = NewAzureIpamInvoker(plugin, &nwInfo)
@ -1004,15 +1004,15 @@ func (plugin *NetPlugin) Delete(args *cniSkel.CmdArgs) error {
}
}
} else if epInfo.EnableInfraVnet {
nwCfg.Ipam.Subnet = nwInfo.Subnets[0].Prefix.String()
nwCfg.Ipam.Address = epInfo.InfraVnetIP.IP.String()
nwCfg.IPAM.Subnet = nwInfo.Subnets[0].Prefix.String()
nwCfg.IPAM.Address = epInfo.InfraVnetIP.IP.String()
err = plugin.ipamInvoker.Delete(nil, nwCfg, args, nwInfo.Options)
if err != nil {
return plugin.RetriableError(fmt.Errorf("failed to release address: %w", err))
}
}
sendEvent(plugin, fmt.Sprintf("CNI DEL succeeded : Released ip %+v podname %v namespace %v", nwCfg.Ipam.Address, k8sPodName, k8sNamespace))
sendEvent(plugin, fmt.Sprintf("CNI DEL succeeded : Released ip %+v podname %v namespace %v", nwCfg.IPAM.Address, k8sPodName, k8sNamespace))
return err
}

Просмотреть файл

@ -35,7 +35,7 @@ func TestMain(m *testing.M) {
Mode: "bridge",
Master: eth0IfName,
IPsToRouteViaHost: []string{"169.254.20.10"},
Ipam: struct {
IPAM: struct {
Mode string `json:"mode,omitempty"`
Type string `json:"type"`
Environment string `json:"environment,omitempty"`

Просмотреть файл

@ -169,7 +169,7 @@ func TestMain(m *testing.M) {
logger.InitLogger(logName, 0, 0, tmpLogDir+"/")
config := common.ServiceConfig{}
httpRestService, err := restserver.NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.NMAgentClientFake{}, nil)
httpRestService, err := restserver.NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.NMAgentClientFake{}, nil, nil)
svc = httpRestService.(*restserver.HTTPRestService)
svc.Name = "cns-test-server"
fakeNNC := v1alpha.NodeNetworkConfig{

Просмотреть файл

@ -0,0 +1,35 @@
package cniconflist
import (
"io"
"github.com/pkg/errors"
)
const (
cniVersion = "0.3.0" //nolint:unused,deadcode,varcheck // used in linux
cniName = "azure" //nolint:unused,deadcode,varcheck // used in linux
cniType = "azure-vnet" //nolint:unused,deadcode,varcheck // used in linux
nodeLocalDNSIP = "169.254.20.10" //nolint:unused,deadcode,varcheck // used in linux
)
// cniConflist represents the containernetworking/cni/pkg/types.NetConfList
type cniConflist struct { //nolint:unused,deadcode // used in linux
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
DisableCheck bool `json:"disableCheck,omitempty"`
Plugins []any `json:"plugins,omitempty"`
}
// V4OverlayGenerator generates the Azure CNI conflist for the ipv4 Overlay scenario
type V4OverlayGenerator struct {
Writer io.WriteCloser
}
func (v *V4OverlayGenerator) Close() error {
if err := v.Writer.Close(); err != nil {
return errors.Wrap(err, "error closing generator")
}
return nil
}

Просмотреть файл

@ -0,0 +1,53 @@
package cniconflist
import (
"encoding/json"
"github.com/Azure/azure-container-networking/cni"
cninet "github.com/Azure/azure-container-networking/cni/network"
"github.com/Azure/azure-container-networking/cni/util"
"github.com/Azure/azure-container-networking/network"
"github.com/pkg/errors"
)
// portmapConfig is the config for the upstream portmap plugin
var portmapConfig any = struct {
Type string `json:"type"`
Capabilities map[string]bool `json:"capabilities"`
SNAT bool `json:"snat"`
}{
Type: "portmap",
Capabilities: map[string]bool{
"portMappings": true,
},
SNAT: true,
}
// Generate writes the CNI conflist to the Generator's output stream
func (v *V4OverlayGenerator) Generate() error {
conflist := cniConflist{
CNIVersion: cniVersion,
Name: cniName,
Plugins: []any{
cni.NetworkConfig{
Type: cniType,
Mode: cninet.OpModeTransparent,
ExecutionMode: string(util.V4Swift),
IPsToRouteViaHost: []string{nodeLocalDNSIP},
IPAM: cni.IPAM{
Type: network.AzureCNS,
Mode: string(util.V4Overlay),
},
},
portmapConfig,
},
}
enc := json.NewEncoder(v.Writer)
enc.SetIndent("", "\t")
if err := enc.Encode(conflist); err != nil {
return errors.Wrap(err, "error encoding conflist to json")
}
return nil
}

Просмотреть файл

@ -0,0 +1,48 @@
package cniconflist_test
import (
"bytes"
"os"
"testing"
"github.com/Azure/azure-container-networking/cns/cniconflist"
"github.com/stretchr/testify/assert"
)
type bufferWriteCloser struct {
*bytes.Buffer
}
func (b *bufferWriteCloser) Close() error {
return nil
}
func TestGenerateV4OverlayConflist(t *testing.T) {
fixture := "testdata/fixtures/azure-linux-swift-overlay.conflist"
buffer := new(bytes.Buffer)
g := cniconflist.V4OverlayGenerator{Writer: &bufferWriteCloser{buffer}}
err := g.Generate()
assert.NoError(t, err)
fixtureBytes, err := os.ReadFile(fixture)
assert.NoError(t, err)
// remove newlines and carriage returns in case these UTs are running on Windows
assert.Equal(t, removeNewLines(fixtureBytes), removeNewLines(buffer.Bytes()))
}
// removeNewLines will remove the newlines and carriage returns from the byte slice
func removeNewLines(b []byte) []byte {
var bb []byte //nolint:prealloc // can't prealloc since we don't know how many bytes will get removed
for _, bs := range b {
if bs == byte('\n') || bs == byte('\r') {
continue
}
bb = append(bb, bs)
}
return bb
}

Просмотреть файл

@ -0,0 +1,11 @@
package cniconflist
import (
"errors"
)
var errNotImplemented = errors.New("cni conflist generator not implemented on Windows")
func (v *V4OverlayGenerator) Generate() error {
return errNotImplemented
}

Просмотреть файл

@ -0,0 +1,30 @@
{
"cniVersion": "0.3.0",
"name": "azure",
"plugins": [
{
"type": "azure-vnet",
"mode": "transparent",
"ipsToRouteViaHost": [
"169.254.20.10"
],
"executionMode": "v4swift",
"ipam": {
"mode": "v4overlay",
"type": "azure-cns"
},
"dns": {},
"runtimeConfig": {
"dns": {}
},
"windowsSettings": {}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
},
"snat": true
}
]
}

Просмотреть файл

@ -38,6 +38,9 @@ type CNSConfig struct {
MSISettings MSISettings
ProgramSNATIPTables bool
ManageEndpointState bool
CNIConflistScenario string
EnableCNIConflistGeneration bool
CNIConflistFilepath string
}
type TelemetrySettings struct {

Просмотреть файл

@ -1359,7 +1359,7 @@ func startService() error {
}
nmagentClient := &fakes.NMAgentClientFake{}
service, err = NewHTTPRestService(&config, &fakes.WireserverClientFake{}, nmagentClient, nil)
service, err = NewHTTPRestService(&config, &fakes.WireserverClientFake{}, nmagentClient, nil, nil)
if err != nil {
return err
}

Просмотреть файл

@ -238,6 +238,11 @@ func (service *HTTPRestService) syncHostNCVersion(ctx context.Context, channelMo
if len(outdatedNCs) > 0 {
return errors.Errorf("unabled to update some NCs: %v, missing or bad response from NMA", outdatedNCs)
}
// if NMA has programmed all the NCs that we expect, we should write the CNI conflist. This will only be done
// once per lifetime of the CNS process. This function is threadsafe and will panic if it fails, so it is safe
// to call in a non-preemptable goroutine.
go service.MustGenerateCNIConflistOnce()
return nil
}

Просмотреть файл

@ -40,7 +40,7 @@ var (
func getTestService() *HTTPRestService {
var config common.ServiceConfig
httpsvc, _ := NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.NMAgentClientFake{}, store.NewMockStore(""))
httpsvc, _ := NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.NMAgentClientFake{}, store.NewMockStore(""), nil)
svc = httpsvc.(*HTTPRestService)
svc.IPAMPoolMonitor = &fakes.MonitorFake{}
setOrchestratorTypeInternal(cns.KubernetesCRD)

Просмотреть файл

@ -64,9 +64,26 @@ type HTTPRestService struct {
state *httpRestServiceState
podsPendingIPAssignment *bounded.TimedSet
sync.RWMutex
dncPartitionKey string
EndpointState map[string]*EndpointInfo // key : container id
EndpointStateStore store.KeyValueStore
dncPartitionKey string
EndpointState map[string]*EndpointInfo // key : container id
EndpointStateStore store.KeyValueStore
cniConflistGenerator CNIConflistGenerator
generateCNIConflistOnce sync.Once
}
type CNIConflistGenerator interface {
Generate() error
Close() error
}
type NoOpConflistGenerator struct{}
func (*NoOpConflistGenerator) Generate() error {
return nil
}
func (*NoOpConflistGenerator) Close() error {
return nil
}
type EndpointInfo struct {
@ -128,7 +145,9 @@ type networkInfo struct {
}
// NewHTTPRestService creates a new HTTP Service object.
func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, nmagentClient nmagentClient, endpointStateStore store.KeyValueStore) (cns.HTTPService, error) {
func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, nmagentClient nmagentClient,
endpointStateStore store.KeyValueStore, gen CNIConflistGenerator,
) (cns.HTTPService, error) {
service, err := cns.NewService(config.Name, config.Version, config.ChannelMode, config.Store)
if err != nil {
return nil, err
@ -164,6 +183,10 @@ func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, nma
podIPIDByPodInterfaceKey := make(map[string]string)
podIPConfigState := make(map[string]cns.IPConfigurationStatus)
if gen == nil {
gen = &NoOpConflistGenerator{}
}
return &HTTPRestService{
Service: service,
store: service.Service.Store,
@ -179,6 +202,7 @@ func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, nma
podsPendingIPAssignment: bounded.NewTimedSet(250), // nolint:gomnd // maxpods
EndpointStateStore: endpointStateStore,
EndpointState: make(map[string]*EndpointInfo),
cniConflistGenerator: gen,
}, nil
}
@ -281,3 +305,17 @@ func (service *HTTPRestService) Stop() {
service.Uninitialize()
logger.Printf("[Azure CNS] Service stopped.")
}
// MustGenerateCNIConflistOnce will generate the CNI conflist once if the service was initialized with
// a conflist generator. If not, this is a no-op.
func (service *HTTPRestService) MustGenerateCNIConflistOnce() {
service.generateCNIConflistOnce.Do(func() {
if err := service.cniConflistGenerator.Generate(); err != nil {
panic("unable to generate cni conflist with error: " + err.Error())
}
if err := service.cniConflistGenerator.Close(); err != nil {
panic("unable to close the cni conflist output stream: " + err.Error())
}
})
}

Просмотреть файл

@ -24,6 +24,7 @@ import (
"github.com/Azure/azure-container-networking/cnm/network"
"github.com/Azure/azure-container-networking/cns"
cnscli "github.com/Azure/azure-container-networking/cns/cmd/cli"
"github.com/Azure/azure-container-networking/cns/cniconflist"
"github.com/Azure/azure-container-networking/cns/cnireconciler"
"github.com/Azure/azure-container-networking/cns/common"
"github.com/Azure/azure-container-networking/cns/configuration"
@ -44,6 +45,7 @@ import (
"github.com/Azure/azure-container-networking/crd/clustersubnetstate/api/v1alpha1"
"github.com/Azure/azure-container-networking/crd/nodenetworkconfig"
"github.com/Azure/azure-container-networking/crd/nodenetworkconfig/api/v1alpha"
"github.com/Azure/azure-container-networking/fs"
"github.com/Azure/azure-container-networking/log"
"github.com/Azure/azure-container-networking/nmagent"
"github.com/Azure/azure-container-networking/platform"
@ -80,6 +82,12 @@ const (
initCNSInitalDelay = 10 * time.Second
)
type cniConflistScenario string
const (
scenarioV4Overlay cniConflistScenario = "v4overlay"
)
var (
rootCtx context.Context
rootErrCh chan error
@ -483,6 +491,23 @@ func main() {
configuration.SetCNSConfigDefaults(cnsconfig)
logger.Printf("[Azure CNS] Read config :%+v", cnsconfig)
var conflistGenerator restserver.CNIConflistGenerator
if cnsconfig.EnableCNIConflistGeneration {
writer, newWriterErr := fs.NewAtomicWriter(cnsconfig.CNIConflistFilepath)
if newWriterErr != nil {
logger.Errorf("unable to create atomic writer to generate cni conflist: %v", newWriterErr)
os.Exit(1)
}
switch scenario := cniConflistScenario(cnsconfig.CNIConflistScenario); scenario {
case scenarioV4Overlay:
conflistGenerator = &cniconflist.V4OverlayGenerator{Writer: writer}
default:
logger.Errorf("unable to generate cni conflist for unknown scenario: %s", scenario)
os.Exit(1)
}
}
// start the health server
z, _ := zap.NewProduction()
go healthserver.Start(z, cnsconfig.MetricsBindAddress)
@ -604,7 +629,8 @@ func main() {
// Create CNS object.
httpRestService, err := restserver.NewHTTPRestService(&config, &wireserver.Client{HTTPClient: &http.Client{}}, nmaClient, endpointStateStore)
httpRestService, err := restserver.NewHTTPRestService(&config, &wireserver.Client{HTTPClient: &http.Client{}}, nmaClient,
endpointStateStore, conflistGenerator)
if err != nil {
logger.Errorf("Failed to create CNS object, err:%v.\n", err)
return

69
fs/atmoic_test.go Normal file
Просмотреть файл

@ -0,0 +1,69 @@
package fs_test
import (
"bufio"
"os"
"testing"
"github.com/Azure/azure-container-networking/fs"
"github.com/stretchr/testify/require"
)
func TestAtomicWriterFileExists(t *testing.T) {
file := "testdata/data.txt"
w, err := fs.NewAtomicWriter(file)
require.NoError(t, err, "error creating atomic writer")
// atomic writer should replace existing file with -old suffix
_, err = os.Stat(file + "-old")
require.NoError(t, err, "error stating old file")
data := []byte("some test data")
_, err = w.Write(data)
require.NoError(t, err, "error writing with atomic writer")
err = w.Close()
require.NoError(t, err, "error closing atomic writer")
dataFile, err := os.Open(file)
require.NoError(t, err, "error opening testdata file")
line, _, err := bufio.NewReader(dataFile).ReadLine()
require.NoError(t, err, "error reading written file")
require.Equal(t, data, line, "testdata doesn't match expected")
}
func TestAtomicWriterNewFile(t *testing.T) {
file := "testdata/newdata.txt"
// if the file exists before running this test, remove it
err := os.Remove(file)
require.NoError(t, ignoreDoesNotExistError(err), "error removing file")
w, err := fs.NewAtomicWriter(file)
require.NoError(t, err, "error creating atomic writer")
data := []byte("some test data")
_, err = w.Write(data)
require.NoError(t, err, "error writing with atomic writer")
err = w.Close()
require.NoError(t, err, "error closing atomic writer")
dataFile, err := os.Open(file)
require.NoError(t, err, "error opening testdata file")
line, _, err := bufio.NewReader(dataFile).ReadLine()
require.NoError(t, err, "error reading written file")
require.Equal(t, data, line, "testdata doesn't match expected")
}
func ignoreDoesNotExistError(err error) error {
if os.IsNotExist(err) {
return nil
}
return err
}

57
fs/atomic.go Normal file
Просмотреть файл

@ -0,0 +1,57 @@
package fs
import (
"io"
"os"
"path"
"github.com/pkg/errors"
)
type AtomicWriter struct {
filename string
tempFile *os.File
}
var _ io.WriteCloser = &AtomicWriter{}
func NewAtomicWriter(filename string) (*AtomicWriter, error) {
exists := true
if _, err := os.Stat(filename); err != nil {
if os.IsNotExist(err) {
exists = false
} else {
return nil, errors.Wrap(err, "unable to stat existing file")
}
}
if exists {
if err := os.Rename(filename, filename+"-old"); err != nil {
return nil, errors.Wrap(err, "unable to move existing file from destination")
}
}
tempFile, err := os.CreateTemp(path.Dir(filename), path.Base(filename)+"*.tmp")
if err != nil {
return nil, errors.Wrap(err, "unable to create temporary file")
}
return &AtomicWriter{filename: filename, tempFile: tempFile}, nil
}
func (a *AtomicWriter) Close() error {
if err := a.tempFile.Close(); err != nil {
return errors.Wrap(err, "unable to close temp file")
}
if err := os.Rename(a.tempFile.Name(), a.filename); err != nil {
return errors.Wrap(err, "unable to move temp file to destination")
}
return nil
}
func (a *AtomicWriter) Write(p []byte) (int, error) {
bs, err := a.tempFile.Write(p)
return bs, errors.Wrap(err, "unable to write to temp file")
}

2
fs/testdata/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
data.txt-old
newdata.txt

1
fs/testdata/data.txt поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
some test data

Просмотреть файл

@ -78,7 +78,7 @@ func ModifyConflists(conflistpath string, installerConf InstallerConfig, perm os
netconfig, conflist, confindex, err := LoadConf(conflistpath)
// change the netconfig from passed installerConf
netconfig.Ipam.Type = installerConf.IPAMType
netconfig.IPAM.Type = installerConf.IPAMType
netconfig.Mode = installerConf.CNIMode
// no bridge in transparent mode