cni/ipam/ipam.go (220 lines of code) (raw):

// Copyright 2017 Microsoft. All rights reserved. // MIT License package ipam import ( "encoding/json" "net" "strconv" "github.com/Azure/azure-container-networking/cni" "github.com/Azure/azure-container-networking/cni/log" "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/ipam" "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" ) const ipamV6 = "azure-vnet-ipamv6" var logger = log.IPamLogger.With(zap.String("component", "cni-ipam")) var ipv4DefaultRouteDstPrefix = net.IPNet{ IP: net.IPv4zero, Mask: net.IPv4Mask(0, 0, 0, 0), } // IpamPlugin represents the CNI IPAM plugin. type ipamPlugin struct { *cni.Plugin am ipam.AddressManager } // NewPlugin creates a new ipamPlugin object. func NewPlugin(name string, config *common.PluginConfig) (*ipamPlugin, error) { // Setup base plugin. plugin, err := cni.NewPlugin(name, config.Version) if err != nil { return nil, err } // Setup address manager. am, err := ipam.NewAddressManager() if err != nil { return nil, err } // Create IPAM plugin. ipamPlg := &ipamPlugin{ Plugin: plugin, am: am, } config.IpamApi = ipamPlg return ipamPlg, nil } // Starts the plugin. func (plugin *ipamPlugin) Start(config *common.PluginConfig) error { // Initialize base plugin. err := plugin.Initialize(config) if err != nil { logger.Error("Failed to initialize base plugin.", zap.Error(err)) return err } // Log platform information. logger.Info("Plugin version.", zap.String("name", plugin.Name), zap.String("version", plugin.Version)) logger.Info("Running on", zap.String("platform", platform.GetOSInfo())) // Initialize address manager. rehyrdration not required on reboot for cni ipam plugin err = plugin.am.Initialize(config, false, plugin.Options) if err != nil { logger.Error("Failed to initialize address manager", zap.Error(err)) return err } logger.Info("Plugin started") return nil } // Stops the plugin. func (plugin *ipamPlugin) Stop() { plugin.am.Uninitialize() plugin.Uninitialize() logger.Info("Plugin stopped") } // Configure parses and applies the given network configuration. func (plugin *ipamPlugin) Configure(stdinData []byte) (*cni.NetworkConfig, error) { // Parse network configuration from stdin. nwCfg, err := cni.ParseNetworkConfig(stdinData) if err != nil { return nil, err } logger.Info("Read network configuration", zap.Any("config", nwCfg)) // Apply IPAM configuration. // Set deployment environment. if nwCfg.IPAM.Environment == "" { nwCfg.IPAM.Environment = common.OptEnvironmentAzure } plugin.SetOption(common.OptEnvironment, nwCfg.IPAM.Environment) // Set query interval. if nwCfg.IPAM.QueryInterval != "" { i, _ := strconv.Atoi(nwCfg.IPAM.QueryInterval) plugin.SetOption(common.OptIpamQueryInterval, i) } err = plugin.am.StartSource(plugin.Options) if err != nil { return nil, err } // Set default address space if not specified. if nwCfg.IPAM.AddrSpace == "" { nwCfg.IPAM.AddrSpace = ipam.LocalDefaultAddressSpaceId } return nwCfg, nil } // // CNI implementation // https://github.com/containernetworking/cni/blob/master/SPEC.md // // Add handles CNI add commands. func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error { var result *cniTypesCurr.Result var err error logger.Info("Processing ADD command", zap.String("ContainerId", args.ContainerID), zap.String("Netns", args.Netns), zap.String("IfName", args.IfName), zap.String("Args", args.Args), zap.String("Path", args.Path), zap.ByteString("StdinData", args.StdinData)) defer func() { logger.Info("ADD command completed", zap.Any("result", result), zap.Error(err)) }() // Parse network configuration from stdin. nwCfg, err := plugin.Configure(args.StdinData) if err != nil { err = plugin.Errorf("Failed to parse network configuration: %v", err) return err } // assign the container id options := make(map[string]string) options[ipam.OptAddressID] = args.ContainerID // Check if an address pool is specified. if nwCfg.IPAM.Subnet == "" { var poolID string var subnet string isIpv6 := false if nwCfg.IPAM.Type == ipamV6 { isIpv6 = true } // Select the requested interface. options[ipam.OptInterfaceName] = nwCfg.Master // Allocate an address pool. 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 } // On failure, release the address pool. defer func() { if err != nil && poolID != "" { logger.Info("Releasing pool", zap.String("poolId", poolID)) _ = plugin.am.ReleasePool(nwCfg.IPAM.AddrSpace, poolID) } }() nwCfg.IPAM.Subnet = subnet logger.Info("Allocated address with subnet", zap.String("poolId", poolID), zap.String("subnet", subnet)) } // Allocate an address for the endpoint. 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 } // On failure, release the address. defer func() { if err != nil && address != "" { logger.Info("Releasing address", zap.String("address", address)) _ = plugin.am.ReleaseAddress(nwCfg.IPAM.AddrSpace, nwCfg.IPAM.Subnet, address, options) } }() logger.Info("Allocated address", zap.String("address", address)) // Parse IP address. ipAddress, err := platform.ConvertStringToIPNet(address) if err != nil { err = plugin.Errorf("Failed to parse address: %v", err) return err } // Query pool information for gateways and DNS servers. 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 } // Populate result. result = &cniTypesCurr.Result{ IPs: []*cniTypesCurr.IPConfig{ { Address: *ipAddress, Gateway: apInfo.Gateway, }, }, Routes: []*cniTypes.Route{ { Dst: ipv4DefaultRouteDstPrefix, GW: apInfo.Gateway, }, }, } // Populate DNS servers. for _, dnsServer := range apInfo.DnsServers { result.DNS.Nameservers = append(result.DNS.Nameservers, dnsServer.String()) } // Convert result to the requested CNI version. res, err := result.GetAsVersion(nwCfg.CNIVersion) if err != nil { err = plugin.Errorf("Failed to convert result: %v", err) return err } // Output the result. if nwCfg.IPAM.Type == cni.Internal { // Called via the internal interface. Pass output back in args. args.StdinData, _ = json.Marshal(res) } else { // Called via the executable interface. Print output to stdout. res.Print() } return nil } // Get handles CNI Get commands. func (plugin *ipamPlugin) Get(args *cniSkel.CmdArgs) error { return nil } // Delete handles CNI delete commands. func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error { var err error logger.Info("Processing DEL command", zap.String("ContainerId", args.ContainerID), zap.String("Netns", args.Netns), zap.String("IfName", args.IfName), zap.String("Args", args.Args), zap.String("Path", args.Path), zap.ByteString("StdinData", args.StdinData)) defer func() { logger.Info("DEL command completed", zap.Error(err)) }() // Parse network configuration from stdin. nwCfg, err := plugin.Configure(args.StdinData) if err != nil { err = plugin.Errorf("Failed to parse network configuration: %v", err) return err } // Select the requested interface. options := make(map[string]string) options[ipam.OptAddressID] = args.ContainerID 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) return err } return nil } // Update handles CNI update command. func (plugin *ipamPlugin) Update(args *cniSkel.CmdArgs) error { return nil }