feat: cns writes cni conflist (#1702)
* 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:
Родитель
8cc8e7f1ff
Коммит
7b91752d10
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
data.txt-old
|
||||
newdata.txt
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче