diff --git a/Makefile b/Makefile index f174c2948a..77eb69086b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,12 @@ SOURCEFILES = \ $(wildcard cni/*.go) \ + $(wildcard cni/ipam/*.go) \ + $(wildcard cni/network/*.go) \ + $(wildcard cni/plugin/*.go) \ $(wildcard cnm/*.go) \ + $(wildcard cnm/ipam/*.go) \ + $(wildcard cnm/network/*.go) \ + $(wildcard cnm/plugin/*.go) \ $(wildcard common/*.go) \ $(wildcard ebtables/*.go) \ $(wildcard ipam/*.go) \ @@ -9,9 +15,9 @@ SOURCEFILES = \ $(wildcard network/*.go) \ $(wildcard store/*.go) -CNIDIR = cni +CNMDIR = cnm/plugin -CNMDIR = cnm +CNIDIR = cni/plugin OUTPUTDIR = out diff --git a/cni/cni.go b/cni/cni.go new file mode 100644 index 0000000000..f4caef2a19 --- /dev/null +++ b/cni/cni.go @@ -0,0 +1,54 @@ +// Copyright Microsoft Corp. +// All rights reserved. + +package cni + +import ( + "encoding/json" + + cniSkel "github.com/containernetworking/cni/pkg/skel" + cniTypes "github.com/containernetworking/cni/pkg/types" +) + +// Plugin is the interface implemented by CNI plugins. +type Plugin interface { + Add(args *cniSkel.CmdArgs) error + Delete(args *cniSkel.CmdArgs) error + + AddImpl(args *cniSkel.CmdArgs, nwCfg *NetworkConfig) (*cniTypes.Result, error) + DeleteImpl(args *cniSkel.CmdArgs, nwCfg *NetworkConfig) (*cniTypes.Result, error) +} + +// NetworkConfig represents the Azure CNI plugin's network configuration. +type NetworkConfig struct { + CniVersion string `json:"cniVersion"` + Name string `json:"name"` + Type string `json:"type"` + Bridge string `json:"bridge"` + IfName string `json:"ifName"` + Ipam struct { + Type string `json:"type"` + AddrSpace string `json:"addressSpace"` + Subnet string `json:"subnet"` + Address string `json:"ipAddress"` + Result string `json:"result"` + } +} + +// ParseNetworkConfig unmarshals network configuration from bytes. +func ParseNetworkConfig(b []byte) (*NetworkConfig, error) { + nwCfg := NetworkConfig{} + + err := json.Unmarshal(b, &nwCfg) + if err != nil { + return nil, err + } + + return &nwCfg, nil +} + +// Serialize marshals a network configuration to bytes. +func (nwcfg *NetworkConfig) Serialize() []byte { + bytes, _ := json.Marshal(nwcfg) + return bytes +} diff --git a/cni/ipam/ipam.go b/cni/ipam/ipam.go new file mode 100644 index 0000000000..9c24083224 --- /dev/null +++ b/cni/ipam/ipam.go @@ -0,0 +1,253 @@ +// Copyright Microsoft Corp. +// All rights reserved. + +package ipam + +import ( + "net" + + "github.com/Azure/azure-container-networking/cni" + "github.com/Azure/azure-container-networking/common" + "github.com/Azure/azure-container-networking/ipam" + "github.com/Azure/azure-container-networking/log" + + cniSkel "github.com/containernetworking/cni/pkg/skel" + cniTypes "github.com/containernetworking/cni/pkg/types" +) + +const ( + // Plugin name. + name = "ipam" + + // The default address space ID used when an explicit ID is not specified. + defaultAddressSpaceId = "LocalDefaultAddressSpace" +) + +// IpamPlugin represents a CNI IPAM plugin. +type ipamPlugin struct { + *common.Plugin + am ipam.AddressManager +} + +// NewPlugin creates a new ipamPlugin object. +func NewPlugin(config *common.PluginConfig) (*ipamPlugin, error) { + // Setup base plugin. + plugin, err := common.NewPlugin(name, config.Version) + if err != nil { + return nil, err + } + + // Setup address manager. + am, err := ipam.NewAddressManager() + if err != nil { + return nil, err + } + + config.IpamApi = am + + return &ipamPlugin{ + Plugin: plugin, + am: am, + }, nil +} + +// Starts the plugin. +func (plugin *ipamPlugin) Start(config *common.PluginConfig) error { + // Initialize base plugin. + err := plugin.Initialize(config) + if err != nil { + log.Printf("[ipam] Failed to initialize base plugin, err:%v.", err) + return err + } + + // Initialize address manager. + environment := plugin.GetOption(common.OptEnvironmentKey) + err = plugin.am.Initialize(config, environment) + if err != nil { + log.Printf("[ipam] Failed to initialize address manager, err:%v.", err) + return err + } + + log.Printf("[ipam] Plugin started.") + + return nil +} + +// Stops the plugin. +func (plugin *ipamPlugin) Stop() { + plugin.am.Uninitialize() + plugin.Uninitialize() + log.Printf("[ipam] Plugin stopped.") +} + +// +// CNI implementation +// https://github.com/containernetworking/cni/blob/master/SPEC.md +// + +// Add handles CNI add commands. +func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error { + log.Printf("[ipam] Processing ADD command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.", + args.ContainerID, args.Netns, args.IfName, args.Args, args.Path) + + // Parse network configuration from stdin. + nwCfg, err := cni.ParseNetworkConfig(args.StdinData) + if err != nil { + log.Printf("[ipam] Failed to parse network configuration: %v.", err) + return nil + } + + log.Printf("[ipam] Read network configuration %+v.", nwCfg) + + // Assume default address space if not specified. + if nwCfg.Ipam.AddrSpace == "" { + nwCfg.Ipam.AddrSpace = defaultAddressSpaceId + } + + // Check if an address pool is specified. + if nwCfg.Ipam.Subnet == "" { + // Allocate an address pool. + poolId, subnet, err := plugin.am.RequestPool(nwCfg.Ipam.AddrSpace, "", "", nil, false) + if err != nil { + log.Printf("[ipam] Failed to allocate pool, err:%v.", err) + return nil + } + + nwCfg.Ipam.Subnet = subnet + log.Printf("[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, nil) + if err != nil { + log.Printf("[ipam] Failed to allocate address, err:%v.", err) + return nil + } + + log.Printf("[ipam] Allocated address %v.", address) + + // Output the result. + ip, cidr, err := net.ParseCIDR(address) + cidr.IP = ip + if err != nil { + log.Printf("[ipam] Failed to parse address, err:%v.", err) + return nil + } + + result := &cniTypes.Result{ + IP4: &cniTypes.IPConfig{IP: *cidr}, + } + + // Output response. + if nwCfg.Ipam.Result == "" { + result.Print() + } else { + args.Args = result.String() + } + + log.Printf("[ipam] ADD succeeded with output %+v.", result) + + return err +} + +// Delete handles CNI delete commands. +func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error { + log.Printf("[ipam] Processing DEL command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.", + args.ContainerID, args.Netns, args.IfName, args.Args, args.Path) + + // Parse network configuration from stdin. + nwCfg, err := cni.ParseNetworkConfig(args.StdinData) + if err != nil { + log.Printf("[ipam] Failed to parse network configuration: %v.", err) + return nil + } + + log.Printf("[ipam] Read network configuration %+v.", nwCfg) + + // Process command. + result, err := plugin.DeleteImpl(args, nwCfg) + if err != nil { + log.Printf("[ipam] Failed to process command: %v.", err) + return nil + } + + // Output response. + if result != nil { + result.Print() + } + + log.Printf("[ipam] DEL succeeded with output %+v.", result) + + return err +} + +// AddImpl handles CNI add commands. +func (plugin *ipamPlugin) AddImpl(args *cniSkel.CmdArgs, nwCfg *cni.NetworkConfig) (*cniTypes.Result, error) { + // Assume default address space if not specified. + if nwCfg.Ipam.AddrSpace == "" { + nwCfg.Ipam.AddrSpace = defaultAddressSpaceId + } + + // Check if an address pool is specified. + if nwCfg.Ipam.Subnet == "" { + // Allocate an address pool. + poolId, subnet, err := plugin.am.RequestPool(nwCfg.Ipam.AddrSpace, "", "", nil, false) + if err != nil { + log.Printf("[ipam] Failed to allocate pool, err:%v.", err) + return nil, err + } + + nwCfg.Ipam.Subnet = subnet + log.Printf("[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, nil) + if err != nil { + log.Printf("[ipam] Failed to allocate address, err:%v.", err) + return nil, err + } + + log.Printf("[ipam] Allocated address %v.", address) + + // Output the result. + ip, cidr, err := net.ParseCIDR(address) + cidr.IP = ip + if err != nil { + log.Printf("[ipam] Failed to parse address, err:%v.", err) + return nil, err + } + + result := &cniTypes.Result{ + IP4: &cniTypes.IPConfig{IP: *cidr}, + } + + return result, nil +} + +// DeleteImpl handles CNI delete commands. +func (plugin *ipamPlugin) DeleteImpl(args *cniSkel.CmdArgs, nwCfg *cni.NetworkConfig) (*cniTypes.Result, error) { + // Assume default address space if not specified. + if nwCfg.Ipam.AddrSpace == "" { + nwCfg.Ipam.AddrSpace = defaultAddressSpaceId + } + + // If an address is specified, release that address. Otherwise, release the pool. + if nwCfg.Ipam.Address != "" { + // Release the address. + err := plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address) + if err != nil { + log.Printf("[cni] Failed to release address, err:%v.", err) + return nil, err + } + } else { + // Release the pool. + err := plugin.am.ReleasePool(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet) + if err != nil { + log.Printf("[cni] Failed to release pool, err:%v.", err) + return nil, err + } + } + + return nil, nil +} diff --git a/cni/network/network.go b/cni/network/network.go new file mode 100644 index 0000000000..7e16d35af2 --- /dev/null +++ b/cni/network/network.go @@ -0,0 +1,250 @@ +// Copyright Microsoft Corp. +// All rights reserved. + +package network + +import ( + "net" + + "github.com/Azure/azure-container-networking/cni" + "github.com/Azure/azure-container-networking/common" + "github.com/Azure/azure-container-networking/ipam" + "github.com/Azure/azure-container-networking/log" + "github.com/Azure/azure-container-networking/network" + + cniSkel "github.com/containernetworking/cni/pkg/skel" + cniTypes "github.com/containernetworking/cni/pkg/types" +) + +const ( + // Plugin name. + name = "net" + + // The default address space ID used when an explicit ID is not specified. + defaultAddressSpaceId = "LocalDefaultAddressSpace" +) + +// NetPlugin object and its interface +type netPlugin struct { + *common.Plugin + nm network.NetworkManager + am ipam.AddressManager +} + +// NewPlugin creates a new netPlugin object. +func NewPlugin(config *common.PluginConfig) (*netPlugin, error) { + // Setup base plugin. + plugin, err := common.NewPlugin(name, config.Version) + if err != nil { + return nil, err + } + + // Setup network manager. + nm, err := network.NewNetworkManager() + if err != nil { + return nil, err + } + + config.NetApi = nm + + return &netPlugin{ + Plugin: plugin, + nm: nm, + }, nil +} + +// Starts the plugin. +func (plugin *netPlugin) Start(config *common.PluginConfig) error { + // Initialize base plugin. + err := plugin.Initialize(config) + if err != nil { + log.Printf("[net] Failed to initialize base plugin, err:%v.", err) + return err + } + + // Initialize network manager. + err = plugin.nm.Initialize(config) + if err != nil { + log.Printf("[net] Failed to initialize network manager, err:%v.", err) + return err + } + + plugin.am, _ = config.IpamApi.(ipam.AddressManager) + + log.Printf("[net] Plugin started.") + + return nil +} + +// Stops the plugin. +func (plugin *netPlugin) Stop() { + plugin.nm.Uninitialize() + plugin.Uninitialize() + log.Printf("[net] Plugin stopped.") +} + +// +// CNI implementation +// https://github.com/containernetworking/cni/blob/master/SPEC.md +// + +// Add handles CNI add commands. +func (plugin *netPlugin) Add(args *cniSkel.CmdArgs) error { + log.Printf("[cni] Processing ADD command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.", + args.ContainerID, args.Netns, args.IfName, args.Args, args.Path) + + // Parse network configuration from stdin. + nwCfg, err := cni.ParseNetworkConfig(args.StdinData) + if err != nil { + log.Printf("[cni] Failed to parse network configuration, err:%v.", err) + return nil + } + + log.Printf("[cni] Read network configuration %+v.", nwCfg) + + // Assume default address space if not specified. + if nwCfg.Ipam.AddrSpace == "" { + nwCfg.Ipam.AddrSpace = defaultAddressSpaceId + } + + // Initialize values from network config. + var poolId string + var subnet string + networkId := nwCfg.Name + endpointId := args.ContainerID + + // Check whether the network already exists. + nwInfo, err := plugin.nm.GetNetworkInfo(networkId) + if err != nil { + // Network does not exist. + log.Printf("[cni] Creating network.") + + // Allocate an address pool for the network. + poolId, subnet, err = plugin.am.RequestPool(nwCfg.Ipam.AddrSpace, "", "", nil, false) + if err != nil { + log.Printf("[cni] Failed to allocate pool, err:%v.", err) + return nil + } + + log.Printf("[cni] Allocated address pool %v with subnet %v.", poolId, subnet) + + // Create the network. + nwInfo := network.NetworkInfo{ + Id: networkId, + Subnets: []string{subnet}, + BridgeName: nwCfg.Bridge, + } + + err = plugin.nm.CreateNetwork(&nwInfo) + if err != nil { + log.Printf("[cni] Failed to create network, err:%v.", err) + return nil + } + + log.Printf("[cni] Created network %v with subnet %v.", networkId, subnet) + } else { + // Network already exists. + log.Printf("[cni] Reusing network and pool.") + + // Query address pool. + poolId = nwInfo.Subnets[0] + subnet = nwInfo.Subnets[0] + } + + // Allocate an address for the endpoint. + address, err := plugin.am.RequestAddress(nwCfg.Ipam.AddrSpace, poolId, "", nil) + if err != nil { + log.Printf("[cni] Failed to request address, err:%v.", err) + return nil + } + + log.Printf("[cni] Allocated address: %v", address) + + // Create the endpoint. + epInfo := network.EndpointInfo{ + Id: endpointId, + IfName: args.IfName, + IPv4Address: address, + NetNsPath: args.Netns, + } + + err = plugin.nm.CreateEndpoint(networkId, &epInfo) + if err != nil { + log.Printf("[cni] Failed to create endpoint, err:%v.", err) + return nil + } + + // Output the result. + ip, cidr, err := net.ParseCIDR(address) + cidr.IP = ip + if err != nil { + log.Printf("[cni] Failed to parse address, err:%v.", err) + return nil + } + + result := &cniTypes.Result{ + IP4: &cniTypes.IPConfig{IP: *cidr}, + } + + result.Print() + + log.Printf("[cni] ADD succeeded with output %+v.", result) + + return nil +} + +// Delete handles CNI delete commands. +func (plugin *netPlugin) Delete(args *cniSkel.CmdArgs) error { + log.Printf("[cni] Processing DEL command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.", + args.ContainerID, args.Netns, args.IfName, args.Args, args.Path) + + // Parse network configuration from stdin. + nwCfg, err := cni.ParseNetworkConfig(args.StdinData) + if err != nil { + log.Printf("[cni] Failed to parse network configuration, err:%v.", err) + return nil + } + + log.Printf("[cni] Read network configuration %+v.", nwCfg) + + // Assume default address space if not specified. + if nwCfg.Ipam.AddrSpace == "" { + nwCfg.Ipam.AddrSpace = defaultAddressSpaceId + } + + // Initialize values from network config. + networkId := nwCfg.Name + endpointId := args.ContainerID + + // Query the network. + nwInfo, err := plugin.nm.GetNetworkInfo(networkId) + if err != nil { + log.Printf("[cni] Failed to query network, err:%v.", err) + return nil + } + + // Query the endpoint. + epInfo, err := plugin.nm.GetEndpointInfo(networkId, endpointId) + if err != nil { + log.Printf("[cni] Failed to query endpoint, err:%v.", err) + return nil + } + + // Delete the endpoint. + err = plugin.nm.DeleteEndpoint(networkId, endpointId) + if err != nil { + log.Printf("[cni] Failed to delete endpoint, err:%v.", err) + return nil + } + + // Release the address. + err = plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwInfo.Subnets[0], epInfo.IPv4Address) + if err != nil { + log.Printf("[cni] Failed to release address, err:%v.", err) + return nil + } + + log.Printf("[cni] DEL succeeded.") + + return nil +} diff --git a/cni/plugin/main.go b/cni/plugin/main.go new file mode 100644 index 0000000000..54e69a33a8 --- /dev/null +++ b/cni/plugin/main.go @@ -0,0 +1,128 @@ +// Copyright Microsoft Corp. +// All rights reserved. + +package main + +import ( + "fmt" + + "github.com/Azure/azure-container-networking/cni/ipam" + "github.com/Azure/azure-container-networking/cni/network" + "github.com/Azure/azure-container-networking/common" + "github.com/Azure/azure-container-networking/log" + "github.com/Azure/azure-container-networking/store" + + cniSkel "github.com/containernetworking/cni/pkg/skel" + cniVers "github.com/containernetworking/cni/pkg/version" +) + +const ( + // Plugin name. + name = "azure-cni-plugin" + + // CNI version supported by this plugin. + cniVersion = "0.2.0" + + // Plugin execution environment. + environment = "azure" + + // JSON file for storing state. + storeFileName = "/etc/cni/azure-container-networking.json" +) + +// Version is populated by make during build. +var version string + +// Main is the entry point for CNI plugin. +func main() { + // Initialize plugin common configuration. + var config common.PluginConfig + config.Name = name + config.Version = version + + // Create network plugin. + netPlugin, err := network.NewPlugin(&config) + if err != nil { + fmt.Printf("[cni] Failed to create network plugin, err:%v.\n", err) + return + } + + // Create IPAM plugin. + ipamPlugin, err := ipam.NewPlugin(&config) + if err != nil { + fmt.Printf("[cni] Failed to create IPAM plugin, err:%v.\n", err) + return + } + + // Create a channel to receive unhandled errors from the plugins. + config.ErrChan = make(chan error, 1) + + // Create the key value store. + config.Store, err = store.NewJsonFileStore(storeFileName) + if err != nil { + log.Printf("[cni] Failed to create store, err:%v.", err) + return + } + + // Acquire store lock. + err = config.Store.Lock(true) + if err != nil { + log.Printf("[cni] Timed out on locking store, err:%v.", err) + return + } + + // Create logging provider. + log.SetLevel(log.LevelInfo) + err = log.SetTarget(log.TargetLogfile) + if err != nil { + fmt.Printf("[cni] Failed to configure logging, err:%v.\n", err) + return + } + + // Log platform information. + log.Printf("[cni] Plugin enter.") + common.LogPlatformInfo() + common.LogNetworkInterfaces() + + // Set plugin options. + ipamPlugin.SetOption(common.OptEnvironmentKey, environment) + + // Start plugins. + if netPlugin != nil { + err = netPlugin.Start(&config) + if err != nil { + fmt.Printf("[cni] Failed to start network plugin, err:%v.\n", err) + return + } + } + + if ipamPlugin != nil { + err = ipamPlugin.Start(&config) + if err != nil { + fmt.Printf("[cni] Failed to start IPAM plugin, err:%v.\n", err) + return + } + } + + // Set supported CNI versions. + pluginInfo := cniVers.PluginSupports(cniVersion) + + // Parse args and call the appropriate cmd handler. + cniSkel.PluginMain(netPlugin.Add, netPlugin.Delete, pluginInfo) + + // Cleanup. + if netPlugin != nil { + netPlugin.Stop() + } + + if ipamPlugin != nil { + ipamPlugin.Stop() + } + + err = config.Store.Unlock() + if err != nil { + log.Printf("[cni] Failed to unlock store, err:%v.", err) + } + + log.Printf("[cni] Plugin exit.") +} diff --git a/common/api.go b/cnm/api.go similarity index 96% rename from common/api.go rename to cnm/api.go index 74c8c161cb..8ccdfc1b7c 100644 --- a/common/api.go +++ b/cnm/api.go @@ -1,7 +1,7 @@ // Copyright Microsoft Corp. // All rights reserved. -package common +package cnm const ( // Libnetwork remote plugin paths diff --git a/cnm/ipam/api.go b/cnm/ipam/api.go new file mode 100644 index 0000000000..6a6cf820a2 --- /dev/null +++ b/cnm/ipam/api.go @@ -0,0 +1,85 @@ +// Copyright Microsoft Corp. +// All rights reserved. + +package ipam + +const ( + // Libnetwork IPAM plugin endpoint type + EndpointType = "IpamDriver" + + // Libnetwork IPAM plugin remote API paths + getCapabilitiesPath = "/IpamDriver.GetCapabilities" + getAddressSpacesPath = "/IpamDriver.GetDefaultAddressSpaces" + requestPoolPath = "/IpamDriver.RequestPool" + releasePoolPath = "/IpamDriver.ReleasePool" + requestAddressPath = "/IpamDriver.RequestAddress" + releaseAddressPath = "/IpamDriver.ReleaseAddress" +) + +// Request sent by libnetwork when querying plugin capabilities. +type getCapabilitiesRequest struct { +} + +// Response sent by plugin when registering its capabilities with libnetwork. +type getCapabilitiesResponse struct { + RequiresMACAddress bool + RequiresRequestReplay bool +} + +// Request sent by libnetwork when querying the default address space names. +type getDefaultAddressSpacesRequest struct { +} + +// Response sent by plugin when returning the default address space names. +type getDefaultAddressSpacesResponse struct { + LocalDefaultAddressSpace string + GlobalDefaultAddressSpace string +} + +// Request sent by libnetwork when acquiring a reference to an address pool. +type requestPoolRequest struct { + AddressSpace string + Pool string + SubPool string + Options map[string]string + V6 bool +} + +// Response sent by plugin when an address pool is successfully referenced. +type requestPoolResponse struct { + PoolID string + Pool string + Data map[string]string +} + +// Request sent by libnetwork when releasing a previously registered address pool. +type releasePoolRequest struct { + PoolID string +} + +// Response sent by plugin when an address pool is successfully released. +type releasePoolResponse struct { +} + +// Request sent by libnetwork when reserving an address from a pool. +type requestAddressRequest struct { + PoolID string + Address string + Options map[string]string +} + +// Response sent by plugin when an address is successfully reserved. +type requestAddressResponse struct { + Address string + Data map[string]string +} + +// Request sent by libnetwork when releasing an address back to the pool. +type releaseAddressRequest struct { + PoolID string + Address string +} + +// Response sent by plugin when an address is successfully released. +type releaseAddressResponse struct { +} diff --git a/ipam/plugin.go b/cnm/ipam/ipam.go similarity index 88% rename from ipam/plugin.go rename to cnm/ipam/ipam.go index a4bec475ff..c0e1988dc8 100644 --- a/ipam/plugin.go +++ b/cnm/ipam/ipam.go @@ -6,7 +6,9 @@ package ipam import ( "net/http" + "github.com/Azure/azure-container-networking/cnm" "github.com/Azure/azure-container-networking/common" + "github.com/Azure/azure-container-networking/ipam" "github.com/Azure/azure-container-networking/log" ) @@ -14,31 +16,31 @@ const ( // Plugin name. name = "ipam" - // Libnetwork IPAM plugin capabilities. + // Plugin capabilities reported to libnetwork. requiresMACAddress = false requiresRequestReplay = false ) -// IpamPlugin object and interface +// IpamPlugin represents a CNM (libnetwork) IPAM plugin. type ipamPlugin struct { - *common.Plugin - am AddressManager + *cnm.Plugin + am ipam.AddressManager } type IpamPlugin interface { common.PluginApi } -// Creates a new IpamPlugin object. +// NewPlugin creates a new IpamPlugin object. func NewPlugin(config *common.PluginConfig) (IpamPlugin, error) { // Setup base plugin. - plugin, err := common.NewPlugin(name, config.Version, endpointType) + plugin, err := cnm.NewPlugin(name, config.Version, EndpointType) if err != nil { return nil, err } // Setup address manager. - am, err := NewAddressManager() + am, err := ipam.NewAddressManager() if err != nil { return nil, err } @@ -51,7 +53,7 @@ func NewPlugin(config *common.PluginConfig) (IpamPlugin, error) { }, nil } -// Starts the plugin. +// Start starts the plugin. func (plugin *ipamPlugin) Start(config *common.PluginConfig) error { // Initialize base plugin. err := plugin.Initialize(config) @@ -82,7 +84,7 @@ func (plugin *ipamPlugin) Start(config *common.PluginConfig) error { return nil } -// Stops the plugin. +// Stop stops the plugin. func (plugin *ipamPlugin) Stop() { plugin.am.Uninitialize() plugin.Uninitialize() @@ -147,7 +149,7 @@ func (plugin *ipamPlugin) requestPool(w http.ResponseWriter, r *http.Request) { // Encode response. data := make(map[string]string) - poolId = NewAddressPoolId(req.AddressSpace, poolId, "").String() + poolId = ipam.NewAddressPoolId(req.AddressSpace, poolId, "").String() resp := requestPoolResponse{PoolID: poolId, Pool: subnet, Data: data} err = plugin.Listener.Encode(w, &resp) @@ -167,7 +169,7 @@ func (plugin *ipamPlugin) releasePool(w http.ResponseWriter, r *http.Request) { } // Process request. - poolId, err := NewAddressPoolIdFromString(req.PoolID) + poolId, err := ipam.NewAddressPoolIdFromString(req.PoolID) if err != nil { plugin.SendErrorResponse(w, err) return @@ -199,7 +201,7 @@ func (plugin *ipamPlugin) requestAddress(w http.ResponseWriter, r *http.Request) } // Process request. - poolId, err := NewAddressPoolIdFromString(req.PoolID) + poolId, err := ipam.NewAddressPoolIdFromString(req.PoolID) if err != nil { plugin.SendErrorResponse(w, err) return @@ -232,7 +234,7 @@ func (plugin *ipamPlugin) releaseAddress(w http.ResponseWriter, r *http.Request) } // Process request. - poolId, err := NewAddressPoolIdFromString(req.PoolID) + poolId, err := ipam.NewAddressPoolIdFromString(req.PoolID) if err != nil { plugin.SendErrorResponse(w, err) return diff --git a/ipam/plugin_test.go b/cnm/ipam/ipam_test.go similarity index 99% rename from ipam/plugin_test.go rename to cnm/ipam/ipam_test.go index 3c58c41fa3..28906bc906 100644 --- a/ipam/plugin_test.go +++ b/cnm/ipam/ipam_test.go @@ -52,7 +52,7 @@ func TestMain(m *testing.M) { mux = plugin.(*ipamPlugin).Listener.GetMux() // Get the internal config sink interface. - sink = plugin.(*ipamPlugin).am.(*addressManager) + sink = plugin.(*ipamPlugin).am // Run tests. exitCode := m.Run() diff --git a/cnm/network/api.go b/cnm/network/api.go new file mode 100644 index 0000000000..22782ca794 --- /dev/null +++ b/cnm/network/api.go @@ -0,0 +1,138 @@ +// Copyright Microsoft Corp. +// All rights reserved. + +package network + +const ( + // Libnetwork network plugin endpoint type + endpointType = "NetworkDriver" + + // Libnetwork network plugin remote API paths + getCapabilitiesPath = "/NetworkDriver.GetCapabilities" + createNetworkPath = "/NetworkDriver.CreateNetwork" + deleteNetworkPath = "/NetworkDriver.DeleteNetwork" + createEndpointPath = "/NetworkDriver.CreateEndpoint" + deleteEndpointPath = "/NetworkDriver.DeleteEndpoint" + joinPath = "/NetworkDriver.Join" + leavePath = "/NetworkDriver.Leave" + endpointOperInfoPath = "/NetworkDriver.EndpointOperInfo" +) + +// Request sent by libnetwork when querying plugin capabilities. +type getCapabilitiesRequest struct { +} + +// Response sent by plugin when registering its capabilities with libnetwork. +type getCapabilitiesResponse struct { + Scope string +} + +// Request sent by libnetwork when creating a new network. +type createNetworkRequest struct { + NetworkID string + Options map[string]interface{} + IPv4Data []ipamData + IPv6Data []ipamData +} + +// IPAMData represents the per-network IP operational information. +type ipamData struct { + AddressSpace string + Pool string + Gateway string + AuxAddresses map[string]string +} + +// Response sent by plugin when a network is created. +type createNetworkResponse struct { +} + +// Request sent by libnetwork when deleting an existing network. +type deleteNetworkRequest struct { + NetworkID string +} + +// Response sent by plugin when a network is deleted. +type deleteNetworkResponse struct { +} + +// Request sent by libnetwork when creating a new endpoint. +type createEndpointRequest struct { + NetworkID string + EndpointID string + Options map[string]interface{} + Interface *endpointInterface +} + +// Represents a libnetwork endpoint interface. +type endpointInterface struct { + Address string + AddressIPv6 string + MacAddress string +} + +// Response sent by plugin when an endpoint is created. +type createEndpointResponse struct { + Interface *endpointInterface +} + +// Request sent by libnetwork when deleting an existing endpoint. +type deleteEndpointRequest struct { + NetworkID string + EndpointID string +} + +// Response sent by plugin when an endpoint is deleted. +type deleteEndpointResponse struct { +} + +// Request sent by libnetwork when joining an endpoint to a sandbox. +type joinRequest struct { + NetworkID string + EndpointID string + SandboxKey string + Options map[string]interface{} +} + +// Response sent by plugin when an endpoint is joined to a sandbox. +type joinResponse struct { + InterfaceName interfaceName + Gateway string + GatewayIPv6 string + StaticRoutes []staticRoute +} + +// Represents naming information for a joined interface. +type interfaceName struct { + SrcName string + DstName string + DstPrefix string +} + +// Represents a static route to be added in a sandbox for a joined interface. +type staticRoute struct { + Destination string + RouteType int + NextHop string +} + +// Request sent by libnetwork when removing an endpoint from its sandbox. +type leaveRequest struct { + NetworkID string + EndpointID string +} + +// Response sent by plugin when an endpoint is removed from its sandbox. +type leaveResponse struct { +} + +// Request sent by libnetwork when querying operational info of an endpoint. +type endpointOperInfoRequest struct { + NetworkID string + EndpointID string +} + +// Response sent by plugin when returning operational info of an endpoint. +type endpointOperInfoResponse struct { + Value map[string]interface{} +} diff --git a/network/plugin.go b/cnm/network/network.go similarity index 93% rename from network/plugin.go rename to cnm/network/network.go index 32c91329be..9782013766 100644 --- a/network/plugin.go +++ b/cnm/network/network.go @@ -6,8 +6,10 @@ package network import ( "net/http" + "github.com/Azure/azure-container-networking/cnm" "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/log" + "github.com/Azure/azure-container-networking/network" ) const ( @@ -18,27 +20,27 @@ const ( scope = "local" ) -// NetPlugin object and its interface +// NetPlugin represents a CNM (libnetwork) network plugin. type netPlugin struct { - *common.Plugin + *cnm.Plugin scope string - nm NetworkManager + nm network.NetworkManager } type NetPlugin interface { common.PluginApi } -// Creates a new NetPlugin object. +// NewPlugin creates a new NetPlugin object. func NewPlugin(config *common.PluginConfig) (NetPlugin, error) { // Setup base plugin. - plugin, err := common.NewPlugin(name, config.Version, endpointType) + plugin, err := cnm.NewPlugin(name, config.Version, endpointType) if err != nil { return nil, err } // Setup network manager. - nm, err := NewNetworkManager() + nm, err := network.NewNetworkManager() if err != nil { return nil, err } @@ -52,7 +54,7 @@ func NewPlugin(config *common.PluginConfig) (NetPlugin, error) { }, nil } -// Starts the plugin. +// Start starts the plugin. func (plugin *netPlugin) Start(config *common.PluginConfig) error { // Initialize base plugin. err := plugin.Initialize(config) @@ -84,7 +86,7 @@ func (plugin *netPlugin) Start(config *common.PluginConfig) error { return nil } -// Stops the plugin. +// Stop stops the plugin. func (plugin *netPlugin) Stop() { plugin.nm.Uninitialize() plugin.Uninitialize() @@ -120,7 +122,7 @@ func (plugin *netPlugin) createNetwork(w http.ResponseWriter, r *http.Request) { } // Process request. - nwInfo := NetworkInfo{ + nwInfo := network.NetworkInfo{ Id: req.NetworkID, Options: req.Options, } @@ -189,7 +191,7 @@ func (plugin *netPlugin) createEndpoint(w http.ResponseWriter, r *http.Request) ipv4Address = req.Interface.Address } - epInfo := EndpointInfo{ + epInfo := network.EndpointInfo{ Id: req.EndpointID, IPv4Address: ipv4Address, } diff --git a/network/plugin_test.go b/cnm/network/network_test.go similarity index 100% rename from network/plugin_test.go rename to cnm/network/network_test.go diff --git a/cnm/plugin.go b/cnm/plugin.go new file mode 100644 index 0000000000..e8b972bbff --- /dev/null +++ b/cnm/plugin.go @@ -0,0 +1,88 @@ +// Copyright Microsoft Corp. +// All rights reserved. + +package cnm + +import ( + "net/http" + + "github.com/Azure/azure-container-networking/common" + "github.com/Azure/azure-container-networking/log" +) + +// Plugin is the parent class for CNM plugins. +type Plugin struct { + *common.Plugin + EndpointType string + Listener *common.Listener +} + +// Creates a new Plugin object. +func NewPlugin(name, version, endpointType string) (*Plugin, error) { + // Setup base plugin. + plugin, err := common.NewPlugin(name, version) + if err != nil { + return nil, err + } + + return &Plugin{ + Plugin: plugin, + EndpointType: endpointType, + }, nil +} + +// Initializes the plugin and starts the listener. +func (plugin *Plugin) Initialize(config *common.PluginConfig) error { + // Initialize the base plugin. + plugin.Plugin.Initialize(config) + + // Create the listener. + var socketName string + if plugin.Name != "test" { + socketName = config.Name + plugin.Name + } + + listener, err := common.NewListener(socketName) + if err != nil { + return err + } + + // Add generic protocol handlers. + listener.AddHandler(activatePath, plugin.activate) + + // Start the listener. + err = listener.Start(config.ErrChan) + plugin.Listener = listener + + return err +} + +// Uninitializes the plugin. +func (plugin *Plugin) Uninitialize() { + plugin.Listener.Stop() + plugin.Plugin.Uninitialize() +} + +// +// Libnetwork remote plugin API +// + +// Handles Activate requests. +func (plugin *Plugin) activate(w http.ResponseWriter, r *http.Request) { + var req activateRequest + + log.Request(plugin.Name, &req, nil) + + resp := activateResponse{[]string{plugin.EndpointType}} + err := plugin.Listener.Encode(w, &resp) + + log.Response(plugin.Name, &resp, err) +} + +// Sends and logs an error response. +func (plugin *Plugin) SendErrorResponse(w http.ResponseWriter, errMsg error) { + resp := errorResponse{errMsg.Error()} + err := plugin.Listener.Encode(w, &resp) + + log.Response(plugin.Name, &resp, err) +} diff --git a/cnm/cnm.go b/cnm/plugin/main.go similarity index 88% rename from cnm/cnm.go rename to cnm/plugin/main.go index 5c28ed5985..070f3f7b3a 100644 --- a/cnm/cnm.go +++ b/cnm/plugin/main.go @@ -9,10 +9,10 @@ import ( "os/signal" "syscall" + "github.com/Azure/azure-container-networking/cnm/ipam" + "github.com/Azure/azure-container-networking/cnm/network" "github.com/Azure/azure-container-networking/common" - "github.com/Azure/azure-container-networking/ipam" "github.com/Azure/azure-container-networking/log" - "github.com/Azure/azure-container-networking/network" "github.com/Azure/azure-container-networking/store" ) @@ -83,11 +83,6 @@ func printVersion() { // Main is the entry point for CNM plugin. func main() { - var netPlugin network.NetPlugin - var ipamPlugin ipam.IpamPlugin - var config common.PluginConfig - var err error - // Initialize and parse command line arguments. common.ParseArgs(&args, printVersion) @@ -102,20 +97,21 @@ func main() { } // Initialize plugin common configuration. + var config common.PluginConfig config.Name = name config.Version = version // Create network plugin. - netPlugin, err = network.NewPlugin(&config) + netPlugin, err := network.NewPlugin(&config) if err != nil { - fmt.Printf("Failed to create network plugin %v\n", err) + fmt.Printf("Failed to create network plugin, err:%v.\n", err) return } // Create IPAM plugin. - ipamPlugin, err = ipam.NewPlugin(&config) + ipamPlugin, err := ipam.NewPlugin(&config) if err != nil { - fmt.Printf("Failed to create IPAM plugin %v\n", err) + fmt.Printf("Failed to create IPAM plugin, err:%v.\n", err) return } @@ -148,7 +144,7 @@ func main() { if netPlugin != nil { err = netPlugin.Start(&config) if err != nil { - fmt.Printf("Failed to start network plugin %v\n", err) + fmt.Printf("Failed to start network plugin, err:%v.\n", err) return } } @@ -156,7 +152,7 @@ func main() { if ipamPlugin != nil { err = ipamPlugin.Start(&config) if err != nil { - fmt.Printf("Failed to start IPAM plugin %v\n", err) + fmt.Printf("Failed to start IPAM plugin, err:%v.\n", err) return } } diff --git a/common/plugin.go b/common/plugin.go index 5d2466f461..93fc26e03e 100644 --- a/common/plugin.go +++ b/common/plugin.go @@ -4,26 +4,23 @@ package common import ( - "net/http" - - "github.com/Azure/azure-container-networking/log" "github.com/Azure/azure-container-networking/store" ) -// Plugin base object. +// Plugin is the parent class that implements behavior common to all plugins. type Plugin struct { - Name string - Version string - EndpointType string - Options map[string]string - Store store.KeyValueStore - Listener *Listener + Name string + Version string + Options map[string]string + ErrChan chan error + Store store.KeyValueStore } // Plugin base interface. type PluginApi interface { Start(*PluginConfig) error Stop() + GetOption(string) string SetOption(string, string) } @@ -37,77 +34,33 @@ type PluginConfig struct { Store store.KeyValueStore } -// Creates a new Plugin object. -func NewPlugin(name, version, endpointType string) (*Plugin, error) { +// NewPlugin creates a new Plugin object. +func NewPlugin(name, version string) (*Plugin, error) { return &Plugin{ Name: name, Version: version, - EndpointType: endpointType, Options: make(map[string]string), }, nil } -// Initializes the plugin and starts the listener. +// Initialize initializes the plugin. func (plugin *Plugin) Initialize(config *PluginConfig) error { - var socketName string - if plugin.Name != "test" { - socketName = config.Name + plugin.Name - } - - // Create the listener. - listener, err := NewListener(socketName) - if err != nil { - return err - } - - // Add generic protocol handlers. - listener.AddHandler(activatePath, plugin.activate) - - // Initialize plugin properties. - plugin.Listener = listener + plugin.ErrChan = config.ErrChan plugin.Store = config.Store - // Start the listener. - err = listener.Start(config.ErrChan) - - return err + return nil } -// Uninitializes the plugin. +// Uninitialize cleans up the plugin. func (plugin *Plugin) Uninitialize() { - plugin.Listener.Stop() -} - -// Sets the option value for the given key. -func (plugin *Plugin) SetOption(key, value string) { - plugin.Options[key] = value } -// Gets the option value for the given key. +// GetOption gets the option value for the given key. func (plugin *Plugin) GetOption(key string) string { return plugin.Options[key] } -// -// Libnetwork remote plugin API -// - -// Handles Activate requests. -func (plugin *Plugin) activate(w http.ResponseWriter, r *http.Request) { - var req activateRequest - - log.Request(plugin.Name, &req, nil) - - resp := activateResponse{[]string{plugin.EndpointType}} - err := plugin.Listener.Encode(w, &resp) - - log.Response(plugin.Name, &resp, err) -} - -// Sends and logs an error response. -func (plugin *Plugin) SendErrorResponse(w http.ResponseWriter, errMsg error) { - resp := errorResponse{errMsg.Error()} - err := plugin.Listener.Encode(w, &resp) - - log.Response(plugin.Name, &resp, err) +// SetOption sets the option value for the given key. +func (plugin *Plugin) SetOption(key, value string) { + plugin.Options[key] = value } diff --git a/ipam/api.go b/ipam/api.go index 3be8069eb2..6bc8f12bc0 100644 --- a/ipam/api.go +++ b/ipam/api.go @@ -7,21 +7,8 @@ import ( "fmt" ) -const ( - // Libnetwork IPAM plugin endpoint type - endpointType = "IpamDriver" - - // Libnetwork IPAM plugin remote API paths - getCapabilitiesPath = "/IpamDriver.GetCapabilities" - getAddressSpacesPath = "/IpamDriver.GetDefaultAddressSpaces" - requestPoolPath = "/IpamDriver.RequestPool" - releasePoolPath = "/IpamDriver.ReleasePool" - requestAddressPath = "/IpamDriver.RequestAddress" - releaseAddressPath = "/IpamDriver.ReleaseAddress" -) - var ( - // Error response messages returned by plugin. + // Error responses returned by AddressManager. errInvalidAddressSpace = fmt.Errorf("Invalid address space") errInvalidPoolId = fmt.Errorf("Invalid address pool") errInvalidAddress = fmt.Errorf("Invalid address") @@ -38,71 +25,3 @@ var ( errAddressNotInUse = fmt.Errorf("Address not in use") errNoAvailableAddresses = fmt.Errorf("No available addresses") ) - -// Request sent by libnetwork when querying plugin capabilities. -type getCapabilitiesRequest struct { -} - -// Response sent by plugin when registering its capabilities with libnetwork. -type getCapabilitiesResponse struct { - RequiresMACAddress bool - RequiresRequestReplay bool -} - -// Request sent by libnetwork when querying the default address space names. -type getDefaultAddressSpacesRequest struct { -} - -// Response sent by plugin when returning the default address space names. -type getDefaultAddressSpacesResponse struct { - LocalDefaultAddressSpace string - GlobalDefaultAddressSpace string -} - -// Request sent by libnetwork when acquiring a reference to an address pool. -type requestPoolRequest struct { - AddressSpace string - Pool string - SubPool string - Options map[string]string - V6 bool -} - -// Response sent by plugin when an address pool is successfully referenced. -type requestPoolResponse struct { - PoolID string - Pool string - Data map[string]string -} - -// Request sent by libnetwork when releasing a previously registered address pool. -type releasePoolRequest struct { - PoolID string -} - -// Response sent by plugin when an address pool is successfully released. -type releasePoolResponse struct { -} - -// Request sent by libnetwork when reserving an address from a pool. -type requestAddressRequest struct { - PoolID string - Address string - Options map[string]string -} - -// Response sent by plugin when an address is successfully reserved. -type requestAddressResponse struct { - Address string - Data map[string]string -} - -// Request sent by libnetwork when releasing an address back to the pool. -type releaseAddressRequest struct { - PoolID string - Address string -} - -// Response sent by plugin when an address is successfully released. -type releaseAddressResponse struct { -} diff --git a/network/api.go b/network/api.go index 5d7246c8db..e55e5a02b3 100644 --- a/network/api.go +++ b/network/api.go @@ -7,23 +7,8 @@ import ( "fmt" ) -const ( - // Libnetwork network plugin endpoint type - endpointType = "NetworkDriver" - - // Libnetwork network plugin remote API paths - getCapabilitiesPath = "/NetworkDriver.GetCapabilities" - createNetworkPath = "/NetworkDriver.CreateNetwork" - deleteNetworkPath = "/NetworkDriver.DeleteNetwork" - createEndpointPath = "/NetworkDriver.CreateEndpoint" - deleteEndpointPath = "/NetworkDriver.DeleteEndpoint" - joinPath = "/NetworkDriver.Join" - leavePath = "/NetworkDriver.Leave" - endpointOperInfoPath = "/NetworkDriver.EndpointOperInfo" -) - var ( - // Error response messages returned by plugin. + // Error responses returned by NetworkManager. errNetworkExists = fmt.Errorf("Network already exists") errNetworkNotFound = fmt.Errorf("Network not found") errEndpointExists = fmt.Errorf("Endpoint already exists") @@ -31,122 +16,3 @@ var ( errEndpointInUse = fmt.Errorf("Endpoint is already joined to a sandbox") errEndpointNotInUse = fmt.Errorf("Endpoint is not joined to a sandbox") ) - -// Request sent by libnetwork when querying plugin capabilities. -type getCapabilitiesRequest struct { -} - -// Response sent by plugin when registering its capabilities with libnetwork. -type getCapabilitiesResponse struct { - Scope string -} - -// Request sent by libnetwork when creating a new network. -type createNetworkRequest struct { - NetworkID string - Options map[string]interface{} - IPv4Data []ipamData - IPv6Data []ipamData -} - -// IPAMData represents the per-network IP operational information. -type ipamData struct { - AddressSpace string - Pool string - Gateway string - AuxAddresses map[string]string -} - -// Response sent by plugin when a network is created. -type createNetworkResponse struct { -} - -// Request sent by libnetwork when deleting an existing network. -type deleteNetworkRequest struct { - NetworkID string -} - -// Response sent by plugin when a network is deleted. -type deleteNetworkResponse struct { -} - -// Request sent by libnetwork when creating a new endpoint. -type createEndpointRequest struct { - NetworkID string - EndpointID string - Options map[string]interface{} - Interface *endpointInterface -} - -// Represents a libnetwork endpoint interface. -type endpointInterface struct { - Address string - AddressIPv6 string - MacAddress string -} - -// Response sent by plugin when an endpoint is created. -type createEndpointResponse struct { - Interface *endpointInterface -} - -// Request sent by libnetwork when deleting an existing endpoint. -type deleteEndpointRequest struct { - NetworkID string - EndpointID string -} - -// Response sent by plugin when an endpoint is deleted. -type deleteEndpointResponse struct { -} - -// Request sent by libnetwork when joining an endpoint to a sandbox. -type joinRequest struct { - NetworkID string - EndpointID string - SandboxKey string - Options map[string]interface{} -} - -// Response sent by plugin when an endpoint is joined to a sandbox. -type joinResponse struct { - InterfaceName interfaceName - Gateway string - GatewayIPv6 string - StaticRoutes []staticRoute -} - -// Represents naming information for a joined interface. -type interfaceName struct { - SrcName string - DstName string - DstPrefix string -} - -// Represents a static route to be added in a sandbox for a joined interface. -type staticRoute struct { - Destination string - RouteType int - NextHop string -} - -// Request sent by libnetwork when removing an endpoint from its sandbox. -type leaveRequest struct { - NetworkID string - EndpointID string -} - -// Response sent by plugin when an endpoint is removed from its sandbox. -type leaveResponse struct { -} - -// Request sent by libnetwork when querying operational info of an endpoint. -type endpointOperInfoRequest struct { - NetworkID string - EndpointID string -} - -// Response sent by plugin when returning operational info of an endpoint. -type endpointOperInfoResponse struct { - Value map[string]interface{} -}