From 512c840d730379e8f1d161b42ff8cc222c7d7c13 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Thu, 1 Dec 2016 13:29:23 -0800 Subject: [PATCH 01/12] Updated network config to omit empty JSON fields --- cni/cni.go | 23 ++++---------- cni/ipam/ipam.go | 78 ++++-------------------------------------------- 2 files changed, 11 insertions(+), 90 deletions(-) diff --git a/cni/cni.go b/cni/cni.go index f4caef2a19..790c6f8819 100644 --- a/cni/cni.go +++ b/cni/cni.go @@ -5,33 +5,20 @@ 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"` + Bridge string `json:"bridge,omitempty"` + IfName string `json:"ifName,omitempty"` Ipam struct { Type string `json:"type"` - AddrSpace string `json:"addressSpace"` - Subnet string `json:"subnet"` - Address string `json:"ipAddress"` - Result string `json:"result"` + AddrSpace string `json:"addressSpace,omitempty"` + Subnet string `json:"subnet,omitempty"` + Address string `json:"ipAddress,omitempty"` } } diff --git a/cni/ipam/ipam.go b/cni/ipam/ipam.go index 9c24083224..15edc38738 100644 --- a/cni/ipam/ipam.go +++ b/cni/ipam/ipam.go @@ -138,12 +138,7 @@ func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error { IP4: &cniTypes.IPConfig{IP: *cidr}, } - // Output response. - if nwCfg.Ipam.Result == "" { - result.Print() - } else { - args.Args = result.String() - } + result.Print() log.Printf("[ipam] ADD succeeded with output %+v.", result) @@ -164,69 +159,6 @@ func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error { 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 @@ -238,16 +170,18 @@ func (plugin *ipamPlugin) DeleteImpl(args *cniSkel.CmdArgs, nwCfg *cni.NetworkCo 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 + return nil } } 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 } } - return nil, nil + log.Printf("[ipam] DEL succeeded.") + + return err } From 6593053bbdf988fdc0547845c1bfc9cbb4bae5f9 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Fri, 2 Dec 2016 17:45:00 -0800 Subject: [PATCH 02/12] Fixed bug that caused stale addresses to remain in pool after update --- ipam/pool.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ipam/pool.go b/ipam/pool.go index 8a1bb5386d..2043e235df 100644 --- a/ipam/pool.go +++ b/ipam/pool.go @@ -157,7 +157,7 @@ func (as *addressSpace) merge(newas *addressSpace) { // This is a new address pool. // Merge it to the existing address space. as.Pools[pk] = pv - delete(newas.Pools, pk) + pv.as = as pv.epoch = as.epoch } else { // This pool already exists. @@ -169,29 +169,39 @@ func (as *addressSpace) merge(newas *addressSpace) { // This is a new address record. // Merge it to the existing address pool. ap.Addresses[ak] = av - delete(ap.Addresses, ak) av.epoch = as.epoch } else { // This address record already exists. ar.epoch = as.epoch } + + delete(pv.Addresses, ak) } - ap.epoch = as.epoch + pv.as = nil } + + delete(newas.Pools, pk) } // Cleanup stale pools and addresses from the old epoch. // Those currently in use will be deleted after they are released. for pk, pv := range as.Pools { if pv.epoch < as.epoch { + // This pool may have stale addresses. for ak, av := range pv.Addresses { - if !av.InUse { + if av.epoch == as.epoch || av.InUse { + // Pool has at least one valid or in-use address. + pv.epoch = as.epoch + } else { + // This address is no longer available. delete(pv.Addresses, ak) } } - if !pv.InUse { + // Delete the pool if it has no addresses left. + if pv.epoch < as.epoch && !pv.InUse { + pv.as = nil delete(as.Pools, pk) } } From 3159a1d5c8c2a62e8ec8c01f1edaafcb59650a16 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Mon, 5 Dec 2016 13:23:35 -0800 Subject: [PATCH 03/12] Added reference counting for shared pools --- ipam/pool.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ipam/pool.go b/ipam/pool.go index 2043e235df..91cdec977c 100644 --- a/ipam/pool.go +++ b/ipam/pool.go @@ -43,7 +43,7 @@ type addressPool struct { Addresses map[string]*addressRecord IsIPv6 bool Priority int - InUse bool + RefCount int epoch int } @@ -200,7 +200,7 @@ func (as *addressSpace) merge(newas *addressSpace) { } // Delete the pool if it has no addresses left. - if pv.epoch < as.epoch && !pv.InUse { + if pv.epoch < as.epoch && !pv.isInUse() { pv.as = nil delete(as.Pools, pk) } @@ -257,18 +257,13 @@ func (as *addressSpace) requestPool(poolId string, subPoolId string, options map if ap == nil { return nil, errAddressPoolNotFound } - - // Fail if requested pool is already in use. - if ap.InUse { - return nil, errAddressPoolInUse - } } else { // Return any available address pool. highestPriority := -1 for _, pool := range as.Pools { // Skip if pool is already in use. - if pool.InUse { + if pool.isInUse() { continue } @@ -289,7 +284,7 @@ func (as *addressSpace) requestPool(poolId string, subPoolId string, options map } } - ap.InUse = true + ap.RefCount++ return ap, nil } @@ -301,14 +296,14 @@ func (as *addressSpace) releasePool(poolId string) error { return errAddressPoolNotFound } - if !ap.InUse { + if !ap.isInUse() { return errAddressPoolNotInUse } - ap.InUse = false + ap.RefCount-- // Delete address pool if it is no longer available. - if ap.epoch < as.epoch { + if ap.epoch < as.epoch && !ap.isInUse() { delete(as.Pools, poolId) } @@ -319,6 +314,11 @@ func (as *addressSpace) releasePool(poolId string) error { // AddressPool // +// Returns if an addess pool is currently in use. +func (ap *addressPool) isInUse() bool { + return ap.RefCount > 0 +} + // Creates a new addressRecord object. func (ap *addressPool) newAddressRecord(addr *net.IP) (*addressRecord, error) { id := addr.String() From e1a19c83ef6d6bb85982447c1f56bc03848cbd40 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Mon, 5 Dec 2016 20:42:09 -0800 Subject: [PATCH 04/12] Moved CNM-specific listener logic to CNM package --- cnm/api.go | 3 +++ cnm/plugin.go | 10 +++++++--- common/listener.go | 47 +++++++++++++++++++++++----------------------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/cnm/api.go b/cnm/api.go index 8ccdfc1b7c..19d8317bf0 100644 --- a/cnm/api.go +++ b/cnm/api.go @@ -4,6 +4,9 @@ package cnm const ( + // Libnetwork plugin path + pluginPath = "/run/docker/plugins" + // Libnetwork remote plugin paths activatePath = "/Plugin.Activate" ) diff --git a/cnm/plugin.go b/cnm/plugin.go index e8b972bbff..b7e945cf46 100644 --- a/cnm/plugin.go +++ b/cnm/plugin.go @@ -5,6 +5,7 @@ package cnm import ( "net/http" + "os" "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/log" @@ -36,13 +37,16 @@ func (plugin *Plugin) Initialize(config *common.PluginConfig) error { // Initialize the base plugin. plugin.Plugin.Initialize(config) + // Create the plugin path. + os.MkdirAll(pluginPath, 0660) + // Create the listener. - var socketName string + var localAddr string if plugin.Name != "test" { - socketName = config.Name + plugin.Name + localAddr = config.Name + plugin.Name } - listener, err := common.NewListener(socketName) + listener, err := common.NewListener("unix", localAddr) if err != nil { return err } diff --git a/common/listener.go b/common/listener.go index 41cd5fea7c..372b97fb48 100644 --- a/common/listener.go +++ b/common/listener.go @@ -9,27 +9,27 @@ import ( "net" "net/http" "os" - "path" "github.com/Azure/azure-container-networking/log" ) -// Libnetwork plugin path -const pluginPath = "/run/docker/plugins" - // Listener object type Listener struct { - socketName string - l net.Listener - mux *http.ServeMux + protocol string + localAddress string + l net.Listener + mux *http.ServeMux } // Creates a new Listener. -func NewListener(socketName string) (*Listener, error) { - var listener Listener +func NewListener(protocol string, localAddress string) (*Listener, error) { + listener := Listener{ + protocol: protocol, + localAddress: localAddress, + } - if socketName != "" { - listener.socketName = path.Join(pluginPath, socketName) + ".sock" + if protocol == "unix" && localAddress != "" { + listener.localAddress = localAddress + ".sock" } listener.mux = http.NewServeMux() @@ -37,25 +37,22 @@ func NewListener(socketName string) (*Listener, error) { return &listener, nil } -// Starts listening for requests from libnetwork and routes them to the corresponding plugin. +// Start creates the listener socket and starts the HTTP server. func (listener *Listener) Start(errChan chan error) error { var err error // Succeed early if no socket was requested. - if listener.socketName == "" { + if listener.localAddress == "" { return nil } - // Create a socket. - os.MkdirAll(pluginPath, 0660) - - listener.l, err = net.Listen("unix", listener.socketName) + listener.l, err = net.Listen(listener.protocol, listener.localAddress) if err != nil { log.Printf("Listener: Failed to listen %+v", err) return err } - log.Printf("Listener: Started listening on %s.", listener.socketName) + log.Printf("[Listener] Started listening on %s.", listener.localAddress) // Launch goroutine for servicing requests. go func() { @@ -69,17 +66,19 @@ func (listener *Listener) Start(errChan chan error) error { func (listener *Listener) Stop() { // Succeed early if no socket was requested. - if listener.socketName == "" { + if listener.localAddress == "" { return } // Stop servicing requests. listener.l.Close() - // Delete the socket. - os.Remove(listener.socketName) + // Delete the unix socket. + if listener.protocol == "unix" { + os.Remove(listener.localAddress) + } - log.Printf("Listener: Stopped listening on %s", listener.socketName) + log.Printf("[Listener] Stopped listening on %s", listener.localAddress) } // Returns the HTTP mux for the listener. @@ -104,7 +103,7 @@ func (listener *Listener) Decode(w http.ResponseWriter, r *http.Request, request if err != nil { http.Error(w, "Failed to decode request: "+err.Error(), http.StatusBadRequest) - log.Printf("Listener: Failed to decode request: %v\n", err.Error()) + log.Printf("[Listener] Failed to decode request: %v\n", err.Error()) } return err } @@ -114,7 +113,7 @@ func (listener *Listener) Encode(w http.ResponseWriter, response interface{}) er err := json.NewEncoder(w).Encode(response) if err != nil { http.Error(w, "Failed to encode response: "+err.Error(), http.StatusInternalServerError) - log.Printf("Listener: Failed to encode response: %v\n", err.Error()) + log.Printf("[Listener] Failed to encode response: %v\n", err.Error()) } return err } From 593429e5d5f31b4738545bf94eab8bdaf440a3d5 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 6 Dec 2016 11:04:31 -0800 Subject: [PATCH 05/12] Added free-form argument support --- common/args.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/common/args.go b/common/args.go index f14e2a7545..4450a2d0dd 100644 --- a/common/args.go +++ b/common/args.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "os" + "strconv" "strings" ) @@ -56,16 +57,28 @@ func ParseArgs(args *ArgumentList, usage func()) { case "bool": arg.Value = arg.boolVal case "string": - arg.strVal = strings.ToLower(arg.strVal) - arg.Value = arg.strVal - if arg.ValueMap[arg.strVal] == nil { - printErrorForArg(arg) + if arg.ValueMap == nil { + // Argument is a free-form string. + arg.Value = arg.strVal + } else { + // Argument must match one of the values in the map. + arg.strVal = strings.ToLower(arg.strVal) + arg.Value = arg.strVal + if arg.ValueMap[arg.strVal] == nil { + printErrorForArg(arg) + } } case "int": - arg.strVal = strings.ToLower(arg.strVal) - arg.Value = arg.ValueMap[arg.strVal] - if arg.Value == nil { - printErrorForArg(arg) + if arg.ValueMap == nil { + // Argument is a free-form integer. + arg.Value, _ = strconv.Atoi(arg.strVal) + } else { + // Argument must match one of the values in the map. + arg.strVal = strings.ToLower(arg.strVal) + arg.Value = arg.ValueMap[arg.strVal] + if arg.Value == nil { + printErrorForArg(arg) + } } } } From 82e8dca13f326c3d1656b8ffa38e63db001cde48 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 6 Dec 2016 11:12:47 -0800 Subject: [PATCH 06/12] Updated arg names to be common between CNI and CNM --- cni/plugin/main.go | 2 +- cnm/plugin/main.go | 61 ++++++++++++++++++++++++---------------------- common/config.go | 41 ++++++++++++++++++++----------- common/listener.go | 2 +- 4 files changed, 61 insertions(+), 45 deletions(-) diff --git a/cni/plugin/main.go b/cni/plugin/main.go index 54e69a33a8..4995464a45 100644 --- a/cni/plugin/main.go +++ b/cni/plugin/main.go @@ -85,7 +85,7 @@ func main() { common.LogNetworkInterfaces() // Set plugin options. - ipamPlugin.SetOption(common.OptEnvironmentKey, environment) + ipamPlugin.SetOption(common.OptEnvironment, environment) // Start plugins. if netPlugin != nil { diff --git a/cnm/plugin/main.go b/cnm/plugin/main.go index 070f3f7b3a..78ecb14f01 100644 --- a/cnm/plugin/main.go +++ b/cnm/plugin/main.go @@ -19,12 +19,6 @@ import ( const ( // Plugin name. name = "azure" - - // Command line options. - OptEnvironment = "environment" - OptLogLevel = "log-level" - OptLogTarget = "log-target" - OptVersion = "version" ) // Version is populated by make during build. @@ -33,42 +27,49 @@ var version string // Command line arguments for CNM plugin. var args = common.ArgumentList{ { - Name: OptEnvironment, - Shorthand: "e", + Name: common.OptEnvironment, + Shorthand: common.OptEnvironmentAlias, Description: "Set the operating environment", Type: "string", - DefaultValue: "azure", + DefaultValue: common.OptEnvironmentAzure, ValueMap: map[string]interface{}{ - "azure": 0, - "mas": 0, + common.OptEnvironmentAzure: 0, + common.OptEnvironmentMAS: 0, }, }, { - Name: OptLogLevel, - Shorthand: "l", + Name: common.OptLogLevel, + Shorthand: common.OptLogLevelAlias, Description: "Set the logging level", Type: "int", - DefaultValue: "info", + DefaultValue: common.OptLogLevelInfo, ValueMap: map[string]interface{}{ - "info": log.LevelInfo, - "debug": log.LevelDebug, + common.OptLogLevelInfo: log.LevelInfo, + common.OptLogLevelDebug: log.LevelDebug, }, }, { - Name: OptLogTarget, - Shorthand: "t", + Name: common.OptLogTarget, + Shorthand: common.OptLogTargetAlias, Description: "Set the logging target", Type: "int", - DefaultValue: "logfile", + DefaultValue: common.OptLogTargetFile, ValueMap: map[string]interface{}{ - "syslog": log.TargetSyslog, - "stderr": log.TargetStderr, - "logfile": log.TargetLogfile, + common.OptLogTargetSyslog: log.TargetSyslog, + common.OptLogTargetStderr: log.TargetStderr, + common.OptLogTargetFile: log.TargetLogfile, }, }, { - Name: OptVersion, - Shorthand: "v", + Name: common.OptIpamQueryInterval, + Shorthand: common.OptIpamQueryIntervalAlias, + Description: "Set the IPAM plugin query interval", + Type: "int", + DefaultValue: "", + }, + { + Name: common.OptVersion, + Shorthand: common.OptVersionAlias, Description: "Print version information", Type: "bool", DefaultValue: false, @@ -86,10 +87,11 @@ func main() { // Initialize and parse command line arguments. common.ParseArgs(&args, printVersion) - environment := common.GetArg(OptEnvironment).(string) - logLevel := common.GetArg(OptLogLevel).(int) - logTarget := common.GetArg(OptLogTarget).(int) - vers := common.GetArg(OptVersion).(bool) + environment := common.GetArg(common.OptEnvironment).(string) + logLevel := common.GetArg(common.OptLogLevel).(int) + logTarget := common.GetArg(common.OptLogTarget).(int) + ipamQueryInterval, _ := common.GetArg(common.OptIpamQueryInterval).(int) + vers := common.GetArg(common.OptVersion).(bool) if vers { printVersion() @@ -138,7 +140,8 @@ func main() { common.LogNetworkInterfaces() // Set plugin options. - ipamPlugin.SetOption(common.OptEnvironmentKey, environment) + ipamPlugin.SetOption(common.OptEnvironment, environment) + ipamPlugin.SetOption(common.OptIpamQueryInterval, ipamQueryInterval) // Start plugins. if netPlugin != nil { diff --git a/common/config.go b/common/config.go index eefe61974b..feac16a2fc 100644 --- a/common/config.go +++ b/common/config.go @@ -6,24 +6,37 @@ package common // Command line options. const ( // Operating environment. - OptEnvironmentKey = "environment" - OptEnvironmentKeyShort = "e" - OptEnvironmentAzure = "azure" - OptEnvironmentMAS = "mas" + OptEnvironment = "environment" + OptEnvironmentAlias = "e" + OptEnvironmentAzure = "azure" + OptEnvironmentMAS = "mas" // Logging level. - OptLogLevelKey = "log-level" - OptLogLevelKeyShort = "l" - OptLogLevelInfo = "info" - OptLogLevelDebug = "debug" + OptLogLevel = "log-level" + OptLogLevelAlias = "l" + OptLogLevelInfo = "info" + OptLogLevelDebug = "debug" // Logging target. - OptLogTargetKey = "log-target" - OptLogTargetKeyShort = "t" - OptLogTargetSyslog = "syslog" - OptLogTargetStderr = "stderr" + OptLogTarget = "log-target" + OptLogTargetAlias = "t" + OptLogTargetSyslog = "syslog" + OptLogTargetStderr = "stderr" + OptLogTargetFile = "logfile" + + // IPAM query URL. + OptIpamQueryUrl = "ipam-query-url" + OptIpamQueryUrlAlias = "u" + + // IPAM query interval. + OptIpamQueryInterval = "ipam-query-interval" + OptIpamQueryIntervalAlias = "i" + + // Version. + OptVersion = "version" + OptVersionAlias = "v" // Help. - OptHelpKey = "help" - OptHelpKeyShort = "?" + OptHelp = "help" + OptHelpAlias = "?" ) diff --git a/common/listener.go b/common/listener.go index 372b97fb48..27a68e2668 100644 --- a/common/listener.go +++ b/common/listener.go @@ -24,7 +24,7 @@ type Listener struct { // Creates a new Listener. func NewListener(protocol string, localAddress string) (*Listener, error) { listener := Listener{ - protocol: protocol, + protocol: protocol, localAddress: localAddress, } From 0ef945eb5641a493f73217297b11a3b05943489d Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 6 Dec 2016 11:17:47 -0800 Subject: [PATCH 07/12] Added ability to pass cmd-line options to IPAM --- cni/ipam/ipam.go | 3 +-- cnm/ipam/ipam.go | 3 +-- common/plugin.go | 16 ++++++++-------- ipam/manager.go | 14 ++++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cni/ipam/ipam.go b/cni/ipam/ipam.go index 15edc38738..37d439586e 100644 --- a/cni/ipam/ipam.go +++ b/cni/ipam/ipam.go @@ -61,8 +61,7 @@ func (plugin *ipamPlugin) Start(config *common.PluginConfig) error { } // Initialize address manager. - environment := plugin.GetOption(common.OptEnvironmentKey) - err = plugin.am.Initialize(config, environment) + err = plugin.am.Initialize(config, plugin.Options) if err != nil { log.Printf("[ipam] Failed to initialize address manager, err:%v.", err) return err diff --git a/cnm/ipam/ipam.go b/cnm/ipam/ipam.go index c0e1988dc8..1a6188cad8 100644 --- a/cnm/ipam/ipam.go +++ b/cnm/ipam/ipam.go @@ -63,8 +63,7 @@ func (plugin *ipamPlugin) Start(config *common.PluginConfig) error { } // Initialize address manager. - environment := plugin.GetOption(common.OptEnvironmentKey) - err = plugin.am.Initialize(config, environment) + err = plugin.am.Initialize(config, plugin.Options) if err != nil { log.Printf("[ipam] Failed to initialize address manager, err:%v.", err) return err diff --git a/common/plugin.go b/common/plugin.go index 93fc26e03e..437a25c513 100644 --- a/common/plugin.go +++ b/common/plugin.go @@ -11,7 +11,7 @@ import ( type Plugin struct { Name string Version string - Options map[string]string + Options map[string]interface{} ErrChan chan error Store store.KeyValueStore } @@ -20,8 +20,8 @@ type Plugin struct { type PluginApi interface { Start(*PluginConfig) error Stop() - GetOption(string) string - SetOption(string, string) + GetOption(string) interface{} + SetOption(string, interface{}) } // Plugin common configuration. @@ -37,9 +37,9 @@ type PluginConfig struct { // NewPlugin creates a new Plugin object. func NewPlugin(name, version string) (*Plugin, error) { return &Plugin{ - Name: name, - Version: version, - Options: make(map[string]string), + Name: name, + Version: version, + Options: make(map[string]interface{}), }, nil } @@ -56,11 +56,11 @@ func (plugin *Plugin) Uninitialize() { } // GetOption gets the option value for the given key. -func (plugin *Plugin) GetOption(key string) string { +func (plugin *Plugin) GetOption(key string) interface{} { return plugin.Options[key] } // SetOption sets the option value for the given key. -func (plugin *Plugin) SetOption(key, value string) { +func (plugin *Plugin) SetOption(key string, value interface{}) { plugin.Options[key] = value } diff --git a/ipam/manager.go b/ipam/manager.go index 8c838cc4da..a8cb4410ed 100644 --- a/ipam/manager.go +++ b/ipam/manager.go @@ -28,7 +28,7 @@ type addressManager struct { // AddressManager API. type AddressManager interface { - Initialize(config *common.PluginConfig, environment string) error + Initialize(config *common.PluginConfig, options map[string]interface{}) error Uninitialize() GetDefaultAddressSpaces() (string, string) @@ -61,7 +61,7 @@ func NewAddressManager() (AddressManager, error) { } // Initialize configures address manager. -func (am *addressManager) Initialize(config *common.PluginConfig, environment string) error { +func (am *addressManager) Initialize(config *common.PluginConfig, options map[string]interface{}) error { am.store = config.Store am.netApi, _ = config.NetApi.(network.NetworkManager) @@ -72,7 +72,7 @@ func (am *addressManager) Initialize(config *common.PluginConfig, environment st } // Start source. - err = am.startSource(environment) + err = am.startSource(options) return err } @@ -129,15 +129,17 @@ func (am *addressManager) save() error { } // Starts configuration source. -func (am *addressManager) startSource(environment string) error { +func (am *addressManager) startSource(options map[string]interface{}) error { var err error + environment, _ := options[common.OptEnvironment].(string) + switch environment { case common.OptEnvironmentAzure: - am.source, err = newAzureSource() + am.source, err = newAzureSource(options) case common.OptEnvironmentMAS: - am.source, err = newMasSource() + am.source, err = newMasSource(options) case "null": am.source, err = newNullSource() From e29f116cf4bba3d19bdcec2cff9d50f2cf53d157 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 6 Dec 2016 11:19:35 -0800 Subject: [PATCH 08/12] Added query URL and interval options to address manager --- ipam/azure.go | 33 ++++++++++++++++++++++++--------- ipam/mas.go | 31 +++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/ipam/azure.go b/ipam/azure.go index adaf8c6a08..6b9c740c03 100644 --- a/ipam/azure.go +++ b/ipam/azure.go @@ -9,22 +9,25 @@ import ( "net/http" "strings" "time" + + "github.com/Azure/azure-container-networking/common" ) const ( // Host URL to query. azureQueryUrl = "http://169.254.169.254/machine/plugins?comp=nmagent&type=getinterfaceinfov1" - // Minimum delay between consecutive polls. - azureDefaultMinPollPeriod = 30 * time.Second + // Minimum time interval between consecutive queries. + azureQueryInterval = 10 * time.Second ) // Microsoft Azure IPAM configuration source. type azureSource struct { name string sink addressConfigSink + queryUrl string + queryInterval time.Duration lastRefresh time.Time - minPollPeriod time.Duration } // Azure host agent XML document format. @@ -49,10 +52,22 @@ type xmlDocument struct { } // Creates the Azure source. -func newAzureSource() (*azureSource, error) { +func newAzureSource(options map[string]interface{}) (*azureSource, error) { + queryUrl, _ := options[common.OptIpamQueryUrl].(string) + if queryUrl == "" { + queryUrl = azureQueryUrl + } + + i, _ := options[common.OptIpamQueryInterval].(int) + queryInterval := time.Duration(i) * time.Second + if queryInterval == 0 { + queryInterval = azureQueryInterval + } + return &azureSource{ name: "Azure", - minPollPeriod: azureDefaultMinPollPeriod, + queryUrl: queryUrl, + queryInterval: queryInterval, }, nil } @@ -71,8 +86,8 @@ func (s *azureSource) stop() { // Refreshes configuration. func (s *azureSource) refresh() error { - // Refresh only if enough time has passed since the last poll. - if time.Since(s.lastRefresh) < s.minPollPeriod { + // Refresh only if enough time has passed since the last query. + if time.Since(s.lastRefresh) < s.queryInterval { return nil } s.lastRefresh = time.Now() @@ -90,7 +105,7 @@ func (s *azureSource) refresh() error { } // Fetch configuration. - resp, err := http.Get(azureQueryUrl) + resp, err := http.Get(s.queryUrl) if err != nil { return err } @@ -115,7 +130,7 @@ func (s *azureSource) refresh() error { for _, iface := range interfaces { macAddr := strings.Replace(iface.HardwareAddr.String(), ":", "", -1) macAddr = strings.ToLower(macAddr) - if macAddr == i.MacAddress { + if macAddr == i.MacAddress || i.MacAddress == "*" { ifName = iface.Name // Prioritize secondary interfaces. diff --git a/ipam/mas.go b/ipam/mas.go index e75ce3e125..6200319b47 100644 --- a/ipam/mas.go +++ b/ipam/mas.go @@ -8,22 +8,25 @@ import ( "net" "net/http" "time" + + "github.com/Azure/azure-container-networking/common" ) const ( // Host URL to query. masQueryUrl = "http://169.254.169.254:6642/ListNetwork" - // Minimum delay between consecutive polls. - masDefaultMinPollPeriod = 30 * time.Second + // Minimum time interval between consecutive queries. + masQueryInterval = 10 * time.Second ) // Microsoft Azure Stack IPAM configuration source. type masSource struct { name string sink addressConfigSink + queryUrl string + queryInterval time.Duration lastRefresh time.Time - minPollPeriod time.Duration } // MAS host agent JSON object format. @@ -39,10 +42,22 @@ type jsonObject struct { } // Creates the MAS source. -func newMasSource() (*masSource, error) { +func newMasSource(options map[string]interface{}) (*masSource, error) { + queryUrl, _ := options[common.OptIpamQueryUrl].(string) + if queryUrl == "" { + queryUrl = masQueryUrl + } + + i, _ := options[common.OptIpamQueryInterval].(int) + queryInterval := time.Duration(i) * time.Second + if queryInterval == 0 { + queryInterval = masQueryInterval + } + return &masSource{ name: "MAS", - minPollPeriod: masDefaultMinPollPeriod, + queryUrl: queryUrl, + queryInterval: queryInterval, }, nil } @@ -61,8 +76,8 @@ func (s *masSource) stop() { // Refreshes configuration. func (s *masSource) refresh() error { - // Refresh only if enough time has passed since the last poll. - if time.Since(s.lastRefresh) < s.minPollPeriod { + // Refresh only if enough time has passed since the last query. + if time.Since(s.lastRefresh) < s.queryInterval { return nil } s.lastRefresh = time.Now() @@ -74,7 +89,7 @@ func (s *masSource) refresh() error { } // Fetch configuration. - resp, err := http.Get(masQueryUrl) + resp, err := http.Get(s.queryUrl) if err != nil { return err } From 2d12f8bdbfba3519e775045294f43a8890ae5ce7 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 6 Dec 2016 11:49:17 -0800 Subject: [PATCH 09/12] Fixed CNM IPAM tests --- cnm/ipam/ipam_test.go | 114 +++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 73 deletions(-) diff --git a/cnm/ipam/ipam_test.go b/cnm/ipam/ipam_test.go index 28906bc906..5c0d1c545d 100644 --- a/cnm/ipam/ipam_test.go +++ b/cnm/ipam/ipam_test.go @@ -18,51 +18,78 @@ import ( var plugin IpamPlugin var mux *http.ServeMux -var sink addressConfigSink - -var local *addressSpace -var global *addressSpace +var ipamQueryUrl = "localhost:42424" +var ipamQueryResponse = "" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "" + +var localAsId string var poolId1 string var address1 string // Wraps the test run with plugin setup and teardown. func TestMain(m *testing.M) { var config common.PluginConfig - var err error + + // Create a fake local agent to handle requests from IPAM plugin. + testAgent, err := common.NewListener("tcp", ipamQueryUrl) + if err != nil { + fmt.Printf("Failed to create agent, err:%v.\n", err) + return + } + testAgent.AddHandler("/", handleIpamQuery) + + err = testAgent.Start(make(chan error, 1)) + if err != nil { + fmt.Printf("Failed to start agent, err:%v.\n", err) + return + } // Create the plugin. plugin, err = 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 } // Configure test mode. plugin.(*ipamPlugin).Name = "test" + plugin.SetOption(common.OptEnvironment, common.OptEnvironmentAzure) + plugin.SetOption(common.OptIpamQueryUrl, "http://"+ipamQueryUrl) // Start the plugin. err = plugin.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 } // Get the internal http mux as test hook. mux = plugin.(*ipamPlugin).Listener.GetMux() - // Get the internal config sink interface. - sink = plugin.(*ipamPlugin).am - // Run tests. exitCode := m.Run() // Cleanup. plugin.Stop() + testAgent.Stop() os.Exit(exitCode) } +// Handles queries from IPAM source. +func handleIpamQuery(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(ipamQueryResponse)) +} + // Decodes plugin's responses to test requests. func decodeResponse(w *httptest.ResponseRecorder, response interface{}) error { if w.Code != http.StatusOK { @@ -83,8 +110,6 @@ func decodeResponse(w *httptest.ResponseRecorder, response interface{}) error { // Tests Plugin.Activate functionality. func TestActivate(t *testing.T) { - fmt.Println("Test: Activate") - var resp struct { Implements []string } @@ -106,8 +131,6 @@ func TestActivate(t *testing.T) { // Tests IpamDriver.GetCapabilities functionality. func TestGetCapabilities(t *testing.T) { - fmt.Println("Test: GetCapabilities") - var resp struct { RequiresMACAddress bool } @@ -127,55 +150,8 @@ func TestGetCapabilities(t *testing.T) { } } -// Tests address space management functionality. -func TestAddAddressSpace(t *testing.T) { - fmt.Println("Test: AddAddressSpace") - - var anyInterface = "any" - var anyPriority = 42 - var err error - - // Configure the local default address space. - local, err = sink.newAddressSpace(localDefaultAddressSpaceId, localScope) - if err != nil { - t.Errorf("newAddressSpace failed %+v", err) - return - } - - addr1 := net.IPv4(192, 168, 1, 1) - addr2 := net.IPv4(192, 168, 1, 2) - subnet := net.IPNet{ - IP: net.IPv4(192, 168, 1, 0), - Mask: net.IPv4Mask(255, 255, 255, 0), - } - ap, err := local.newAddressPool(anyInterface, anyPriority, &subnet) - ap.newAddressRecord(&addr1) - ap.newAddressRecord(&addr2) - - addr1 = net.IPv4(192, 168, 2, 1) - subnet = net.IPNet{ - IP: net.IPv4(192, 168, 2, 0), - Mask: net.IPv4Mask(255, 255, 255, 0), - } - ap, err = local.newAddressPool(anyInterface, anyPriority, &subnet) - ap.newAddressRecord(&addr1) - - sink.setAddressSpace(local) - - // Configure the global default address space. - global, err = sink.newAddressSpace(globalDefaultAddressSpaceId, globalScope) - if err != nil { - t.Errorf("newAddressSpace failed %+v", err) - return - } - - sink.setAddressSpace(global) -} - // Tests IpamDriver.GetDefaultAddressSpaces functionality. func TestGetDefaultAddressSpaces(t *testing.T) { - fmt.Println("Test: GetDefaultAddressSpaces") - var resp getDefaultAddressSpacesResponse req, err := http.NewRequest(http.MethodGet, getAddressSpacesPath, nil) @@ -188,22 +164,20 @@ func TestGetDefaultAddressSpaces(t *testing.T) { err = decodeResponse(w, &resp) - if err != nil || - resp.LocalDefaultAddressSpace != localDefaultAddressSpaceId || - resp.GlobalDefaultAddressSpace != globalDefaultAddressSpaceId { + if err != nil || resp.LocalDefaultAddressSpace == "" { t.Errorf("GetDefaultAddressSpaces response is invalid %+v", resp) } + + localAsId = resp.LocalDefaultAddressSpace } // Tests IpamDriver.RequestPool functionality. func TestRequestPool(t *testing.T) { - fmt.Println("Test: RequestPool") - var body bytes.Buffer var resp requestPoolResponse payload := &requestPoolRequest{ - AddressSpace: localDefaultAddressSpaceId, + AddressSpace: localAsId, } json.NewEncoder(&body).Encode(payload) @@ -227,8 +201,6 @@ func TestRequestPool(t *testing.T) { // Tests IpamDriver.RequestAddress functionality. func TestRequestAddress(t *testing.T) { - fmt.Println("Test: RequestAddress") - var body bytes.Buffer var resp requestAddressResponse @@ -260,8 +232,6 @@ func TestRequestAddress(t *testing.T) { // Tests IpamDriver.ReleaseAddress functionality. func TestReleaseAddress(t *testing.T) { - fmt.Println("Test: ReleaseAddress") - var body bytes.Buffer var resp releaseAddressResponse @@ -289,8 +259,6 @@ func TestReleaseAddress(t *testing.T) { // Tests IpamDriver.ReleasePool functionality. func TestReleasePool(t *testing.T) { - fmt.Println("Test: ReleasePool") - var body bytes.Buffer var resp releasePoolResponse From ae72f2fa32064838676f42b36b86792e3e74d368 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 6 Dec 2016 12:27:16 -0800 Subject: [PATCH 10/12] Made address pool selection more deterministic --- ipam/pool.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ipam/pool.go b/ipam/pool.go index 91cdec977c..da07141ba4 100644 --- a/ipam/pool.go +++ b/ipam/pool.go @@ -253,6 +253,7 @@ func (as *addressSpace) requestPool(poolId string, subPoolId string, options map if poolId != "" { // Return the specific address pool requested. + // Note sharing of pools is allowed when specifically requested. ap = as.Pools[poolId] if ap == nil { return nil, errAddressPoolNotFound @@ -260,6 +261,7 @@ func (as *addressSpace) requestPool(poolId string, subPoolId string, options map } else { // Return any available address pool. highestPriority := -1 + highestNumAddr := -1 for _, pool := range as.Pools { // Skip if pool is already in use. @@ -272,11 +274,17 @@ func (as *addressSpace) requestPool(poolId string, subPoolId string, options map continue } - // Pick the pool with the highest priority. + // Prefer the pool with the highest priority. if pool.Priority > highestPriority { highestPriority = pool.Priority ap = pool } + + // Prefer the pool with the highest number of addresses. + if len(pool.Addresses) > highestNumAddr { + highestNumAddr = len(pool.Addresses) + ap = pool + } } if ap == nil { @@ -361,6 +369,7 @@ func (ap *addressPool) requestAddress(address string, options map[string]string) if !ar.InUse { break } + ar = nil } if ar == nil { From d5f48044b3471d35d4746e0a80921f03d096b693 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 6 Dec 2016 16:31:06 -0800 Subject: [PATCH 11/12] Added address manager tests --- ipam/manager_test.go | 373 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 ipam/manager_test.go diff --git a/ipam/manager_test.go b/ipam/manager_test.go new file mode 100644 index 0000000000..21fee5823a --- /dev/null +++ b/ipam/manager_test.go @@ -0,0 +1,373 @@ +// Copyright Microsoft Corp. +// All rights reserved. + +package ipam + +import ( + "fmt" + "net" + "testing" + + "github.com/Azure/azure-container-networking/common" +) + +var ( + anyInterface = "any" + anyPriority = 42 + + // Pools and addresses used by tests. + subnet1 = net.IPNet{IP: net.IPv4(10, 0, 1, 0), Mask: net.IPv4Mask(255, 255, 255, 0)} + addr11 = net.IPv4(10, 0, 1, 1) + addr12 = net.IPv4(10, 0, 1, 2) + addr13 = net.IPv4(10, 0, 1, 3) + + subnet2 = net.IPNet{IP: net.IPv4(10, 0, 2, 0), Mask: net.IPv4Mask(255, 255, 255, 0)} + addr21 = net.IPv4(10, 0, 2, 1) + addr22 = net.IPv4(10, 0, 2, 2) + addr23 = net.IPv4(10, 0, 2, 3) + + subnet3 = net.IPNet{IP: net.IPv4(10, 0, 3, 0), Mask: net.IPv4Mask(255, 255, 255, 0)} + addr31 = net.IPv4(10, 0, 3, 1) + addr32 = net.IPv4(10, 0, 3, 2) + addr33 = net.IPv4(10, 0, 3, 3) +) + +// createAddressManager creates an address manager with a simple test configuration. +func createAddressManager() (AddressManager, error) { + var config common.PluginConfig + var options map[string]interface{} + + am, err := NewAddressManager() + if err != nil { + return nil, err + } + + err = am.Initialize(&config, options) + if err != nil { + return nil, err + } + + err = setupTestAddressSpace(am) + if err != nil { + return nil, err + } + + return am, nil +} + +// dumpAddressManager dumps the contents of an address manager. +func dumpAddressManager(am AddressManager) { + amImpl := am.(*addressManager) + fmt.Printf("AddressManager:%+v\n", amImpl) + for sk, sv := range amImpl.AddrSpaces { + fmt.Printf("AddressSpace %v:%+v\n", sk, sv) + for pk, pv := range sv.Pools { + fmt.Printf("\tPool %v:%+v\n", pk, pv) + for ak, av := range pv.Addresses { + fmt.Printf("\t\tAddress %v:%+v\n", ak, av) + } + } + } +} + +// setupTestAddressSpace creates a simple address space used by various tests. +func setupTestAddressSpace(am AddressManager) error { + var anyInterface = "any" + var anyPriority = 42 + + amImpl := am.(*addressManager) + + // Configure an empty global address space. + globalAs, err := amImpl.newAddressSpace(globalDefaultAddressSpaceId, globalScope) + if err != nil { + return err + } + + err = amImpl.setAddressSpace(globalAs) + if err != nil { + return err + } + + // Configure a local address space. + localAs, err := amImpl.newAddressSpace(localDefaultAddressSpaceId, localScope) + if err != nil { + return err + } + + // Add subnet1 with addresses addr11 and addr12. + ap, err := localAs.newAddressPool(anyInterface, anyPriority, &subnet1) + ap.newAddressRecord(&addr11) + ap.newAddressRecord(&addr12) + + // Add subnet2 with addr21. + ap, err = localAs.newAddressPool(anyInterface, anyPriority, &subnet2) + ap.newAddressRecord(&addr21) + + amImpl.setAddressSpace(localAs) + + return nil +} + +// cleanupTestAddressSpace deletes any existing address spaces. +func cleanupTestAddressSpace(am AddressManager) error { + amImpl := am.(*addressManager) + + // Configure an empty local address space. + localAs, err := amImpl.newAddressSpace(localDefaultAddressSpaceId, localScope) + if err != nil { + return err + } + + err = amImpl.setAddressSpace(localAs) + if err != nil { + return err + } + + // Configure an empty global address space. + globalAs, err := amImpl.newAddressSpace(globalDefaultAddressSpaceId, globalScope) + if err != nil { + return err + } + + err = amImpl.setAddressSpace(globalAs) + if err != nil { + return err + } + + return nil +} + +// +// Address manager tests. +// + +// Tests address spaces are created and queried correctly. +func TestAddressSpaceCreateAndGet(t *testing.T) { + // Start with the test address space. + am, err := createAddressManager() + if err != nil { + t.Fatalf("createAddressManager failed, err:%+v.", err) + } + + // Test if the address spaces are returned correctly. + local, global := am.GetDefaultAddressSpaces() + + if local != localDefaultAddressSpaceId { + t.Errorf("GetDefaultAddressSpaces returned invalid local address space.") + } + + if global != globalDefaultAddressSpaceId { + t.Errorf("GetDefaultAddressSpaces returned invalid global address space.") + } +} + +// Tests updating an existing address space adds new resources and removes stale ones. +func TestAddressSpaceUpdate(t *testing.T) { + // Start with the test address space. + am, err := createAddressManager() + if err != nil { + t.Fatalf("createAddressManager failed, err:%+v.", err) + } + amImpl := am.(*addressManager) + + // Create a new local address space to update the existing one. + localAs, err := amImpl.newAddressSpace(localDefaultAddressSpaceId, localScope) + if err != nil { + t.Errorf("newAddressSpace failed, err:%+v.", err) + } + + // Remove addr12 and add addr13 in subnet1. + ap, err := localAs.newAddressPool(anyInterface, anyPriority, &subnet1) + ap.newAddressRecord(&addr11) + ap.newAddressRecord(&addr13) + + // Remove subnet2. + // Add subnet3 with addr31. + ap, err = localAs.newAddressPool(anyInterface, anyPriority, &subnet3) + ap.newAddressRecord(&addr31) + + err = amImpl.setAddressSpace(localAs) + if err != nil { + t.Errorf("setAddressSpace failed, err:%+v.", err) + } + + // Test that the address space was updated correctly. + localAs, err = amImpl.getAddressSpace(localDefaultAddressSpaceId) + if err != nil { + t.Errorf("getAddressSpace failed, err:%+v.", err) + } + + // Subnet1 should have addr11 and addr13, but not addr12. + ap, err = localAs.getAddressPool(subnet1.String()) + if err != nil { + t.Errorf("Cannot find subnet1, err:%+v.", err) + } + + _, err = ap.requestAddress(addr11.String(), nil) + if err != nil { + t.Errorf("Cannot find addr11, err:%+v.", err) + } + + _, err = ap.requestAddress(addr12.String(), nil) + if err == nil { + t.Errorf("Found addr12.") + } + + _, err = ap.requestAddress(addr13.String(), nil) + if err != nil { + t.Errorf("Cannot find addr13, err:%+v.", err) + } + + // Subnet2 should not exist. + ap, err = localAs.getAddressPool(subnet2.String()) + if err == nil { + t.Errorf("Found subnet2.") + } + + // Subnet3 should have addr31 only. + ap, err = localAs.getAddressPool(subnet3.String()) + if err != nil { + t.Errorf("Cannot find subnet3, err:%+v.", err) + } + + _, err = ap.requestAddress(addr31.String(), nil) + if err != nil { + t.Errorf("Cannot find addr31, err:%+v.", err) + } + + _, err = ap.requestAddress(addr32.String(), nil) + if err == nil { + t.Errorf("Found addr32.") + } +} + +// Tests multiple wildcard address pool requests return separate pools. +func TestAddressPoolRequestsForSeparatePools(t *testing.T) { + // Start with the test address space. + am, err := createAddressManager() + if err != nil { + t.Fatalf("createAddressManager failed, err:%+v.", err) + } + + // Request two separate address pools. + poolId1, subnet1, err := am.RequestPool(localDefaultAddressSpaceId, "", "", nil, false) + if err != nil { + t.Errorf("RequestPool failed, err:%v", err) + } + + poolId2, subnet2, err := am.RequestPool(localDefaultAddressSpaceId, "", "", nil, false) + if err != nil { + t.Errorf("RequestPool failed, err:%v", err) + } + + // Test the poolIds and subnets do not match. + if poolId1 == poolId2 || subnet1 == subnet2 { + t.Errorf("Pool requests returned the same pool.") + } + + // Release the address pools. + err = am.ReleasePool(localDefaultAddressSpaceId, poolId1) + if err != nil { + t.Errorf("ReleasePool failed, err:%v", err) + } + + err = am.ReleasePool(localDefaultAddressSpaceId, poolId2) + if err != nil { + t.Errorf("ReleasePool failed, err:%v", err) + } +} + +// Tests multiple identical address pool requests return the same pool and pools are referenced correctly. +func TestAddressPoolRequestsForSamePool(t *testing.T) { + // Start with the test address space. + am, err := createAddressManager() + if err != nil { + t.Fatalf("createAddressManager failed, err:%+v.", err) + } + + // Request the same address pool twice. + poolId1, subnet1, err := am.RequestPool(localDefaultAddressSpaceId, "", "", nil, false) + if err != nil { + t.Errorf("RequestPool failed, err:%v", err) + } + + poolId2, subnet2, err := am.RequestPool(localDefaultAddressSpaceId, poolId1, "", nil, false) + if err != nil { + t.Errorf("RequestPool failed, err:%v", err) + } + + // Test the subnets do not match. + if poolId1 != poolId2 || subnet1 != subnet2 { + t.Errorf("Pool requests returned different pools.") + } + + // Release the address pools. + err = am.ReleasePool(localDefaultAddressSpaceId, poolId1) + if err != nil { + t.Errorf("ReleasePool failed, err:%v", err) + } + + err = am.ReleasePool(localDefaultAddressSpaceId, poolId2) + if err != nil { + t.Errorf("ReleasePool failed, err:%v", err) + } + + // Third release should fail. + err = am.ReleasePool(localDefaultAddressSpaceId, poolId1) + if err == nil { + t.Errorf("ReleasePool succeeded extra, err:%v", err) + } +} + +// Tests address requests from the same pool return separate addresses and releases work correctly. +func TestAddressRequestsFromTheSamePool(t *testing.T) { + // Start with the test address space. + am, err := createAddressManager() + if err != nil { + t.Fatalf("createAddressManager failed, err:%+v.", err) + } + + // Request a pool. + poolId, _, err := am.RequestPool(localDefaultAddressSpaceId, "", "", nil, false) + if err != nil { + t.Errorf("RequestPool failed, err:%v", err) + } + + // Request two addresses from the pool. + address1, err := am.RequestAddress(localDefaultAddressSpaceId, poolId, "", nil) + if err != nil { + t.Errorf("RequestAddress failed, err:%v", err) + } + + addr, _, _ := net.ParseCIDR(address1) + address1 = addr.String() + + address2, err := am.RequestAddress(localDefaultAddressSpaceId, poolId, "", nil) + if err != nil { + t.Errorf("RequestAddress failed, err:%v", err) + } + + addr, _, _ = net.ParseCIDR(address2) + address2 = addr.String() + + // Test the addresses do not match. + if address1 == address2 { + t.Errorf("Address requests returned the same address %v.", address1) + } + + // Release addresses and the pool. + err = am.ReleaseAddress(localDefaultAddressSpaceId, poolId, address1) + if err != nil { + t.Errorf("ReleaseAddress failed, err:%v", err) + } + + err = am.ReleaseAddress(localDefaultAddressSpaceId, poolId, address2) + if err != nil { + t.Errorf("ReleaseAddress failed, err:%v", err) + } + + err = am.ReleasePool(localDefaultAddressSpaceId, poolId) + if err != nil { + t.Errorf("ReleasePool failed, err:%v", err) + } +} From d8b65469b7ba2a322b00450aee2f09dd40617ff0 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 6 Dec 2016 17:01:20 -0800 Subject: [PATCH 12/12] Moved libnetwork remote plugin directory creation to CNM package --- cnm/plugin.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cnm/plugin.go b/cnm/plugin.go index b7e945cf46..cc6cd51cb0 100644 --- a/cnm/plugin.go +++ b/cnm/plugin.go @@ -6,6 +6,7 @@ package cnm import ( "net/http" "os" + "path" "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/log" @@ -43,7 +44,7 @@ func (plugin *Plugin) Initialize(config *common.PluginConfig) error { // Create the listener. var localAddr string if plugin.Name != "test" { - localAddr = config.Name + plugin.Name + localAddr = path.Join(pluginPath, config.Name+plugin.Name) } listener, err := common.NewListener("unix", localAddr)