1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request from aboch/am

IPAM driver
This commit is contained in:
Madhu Venugopal 2015-10-03 17:35:42 -07:00
commit 8dde3b2380
44 changed files with 2990 additions and 1195 deletions

View file

@ -29,7 +29,8 @@ cmd/dnet/dnet
*.tmp
*.coverprofile
# IDE files
# IDE files and folders
.project
.settings/
libnetwork-build.created

View file

@ -306,8 +306,23 @@ func (h *Handle) validateOrdinal(ordinal uint32) error {
}
// Destroy removes from the datastore the data belonging to this handle
func (h *Handle) Destroy() {
h.deleteFromStore()
func (h *Handle) Destroy() error {
for {
if err := h.deleteFromStore(); err != nil {
if _, ok := err.(types.RetryError); !ok {
return fmt.Errorf("internal failure while destroying the sequence: %v", err)
}
// Fetch latest
if err := h.store.GetObject(datastore.Key(h.Key()...), h); err != nil {
if err == datastore.ErrKeyNotFound { // already removed
return nil
}
return fmt.Errorf("failed to fetch from store when destroying the sequence: %v", err)
}
continue
}
return nil
}
}
// ToByteArray converts this handle's data into a byte array

View file

@ -10,6 +10,7 @@ import (
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/drivers/overlay"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
@ -18,7 +19,7 @@ type router struct {
}
type endpoint struct {
addr net.IPNet
addr *net.IPNet
mac net.HardwareAddr
name string
}
@ -32,9 +33,40 @@ func (ep *endpoint) Interface() driverapi.InterfaceInfo {
return nil
}
func (ep *endpoint) AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
ep.addr = ipv4
ep.mac = mac
func (ep *endpoint) SetMacAddress(mac net.HardwareAddr) error {
if ep.mac != nil {
return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", ep.mac, mac)
}
if mac == nil {
return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
}
ep.mac = types.GetMacCopy(mac)
return nil
}
func (ep *endpoint) SetIPAddress(address *net.IPNet) error {
if address.IP == nil {
return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
}
if address.IP.To4() == nil {
return types.NotImplementedErrorf("do not support ipv6 yet")
}
if ep.addr != nil {
return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with %s.", ep.addr, address)
}
ep.addr = types.GetIPNetCopy(address)
return nil
}
func (ep *endpoint) MacAddress() net.HardwareAddr {
return types.GetMacCopy(ep.mac)
}
func (ep *endpoint) Address() *net.IPNet {
return types.GetIPNetCopy(ep.addr)
}
func (ep *endpoint) AddressIPv6() *net.IPNet {
return nil
}
@ -86,7 +118,7 @@ func main() {
}
if err := r.d.CreateNetwork("testnetwork",
map[string]interface{}{}); err != nil {
map[string]interface{}{}, nil, nil); err != nil {
fmt.Printf("Failed to create network in the driver: %v\n", err)
os.Exit(1)
}
@ -111,7 +143,7 @@ func main() {
os.Exit(1)
}
ipAddr := &netlink.Addr{IPNet: &ep.addr, Label: ""}
ipAddr := &netlink.Addr{IPNet: ep.addr, Label: ""}
if err := netlink.AddrAdd(link, ipAddr); err != nil {
fmt.Printf("Failed to add address to the interface: %v\n", err)
os.Exit(1)

View file

@ -58,6 +58,7 @@ import (
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/hostdiscovery"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/osl"
"github.com/docker/libnetwork/types"
)
@ -116,7 +117,14 @@ type driverData struct {
capability driverapi.Capability
}
type ipamData struct {
driver ipamapi.Ipam
// default address spaces are provided by ipam driver at registration time
defaultLocalAddressSpace, defaultGlobalAddressSpace string
}
type driverTable map[string]*driverData
type ipamTable map[string]*ipamData
type networkTable map[string]*network
type endpointTable map[string]*endpoint
type sandboxTable map[string]*sandbox
@ -125,6 +133,7 @@ type controller struct {
id string
networks networkTable
drivers driverTable
ipamDrivers ipamTable
sandboxes sandboxTable
cfg *config.Config
globalStore, localStore datastore.DataStore
@ -145,11 +154,12 @@ func New(cfgOptions ...config.Option) (NetworkController, error) {
cfg.ProcessOptions(cfgOptions...)
}
c := &controller{
id: stringid.GenerateRandomID(),
cfg: cfg,
networks: networkTable{},
sandboxes: sandboxTable{},
drivers: driverTable{}}
id: stringid.GenerateRandomID(),
cfg: cfg,
networks: networkTable{},
sandboxes: sandboxTable{},
drivers: driverTable{},
ipamDrivers: ipamTable{}}
if err := initDrivers(c); err != nil {
return nil, err
}
@ -160,13 +170,26 @@ func New(cfgOptions ...config.Option) (NetworkController, error) {
// But it cannot fail creating the Controller
log.Debugf("Failed to Initialize Datastore due to %v. Operating in non-clustered mode", err)
}
if err := c.initLocalStore(); err != nil {
log.Debugf("Failed to Initialize LocalDatastore due to %v.", err)
}
}
if err := initIpams(c, c.localStore, c.globalStore); err != nil {
return nil, err
}
if cfg != nil {
if err := c.restoreFromGlobalStore(); err != nil {
log.Debugf("Failed to restore from global Datastore due to %v", err)
}
if err := c.initDiscovery(cfg.Cluster.Watcher); err != nil {
// Failing to initalize discovery is a bad situation to be in.
// But it cannot fail creating the Controller
log.Debugf("Failed to Initialize Discovery : %v", err)
}
if err := c.initLocalStore(); err != nil {
log.Debugf("Failed to Initialize LocalDatastore due to %v.", err)
if err := c.restoreFromLocalStore(); err != nil {
log.Debugf("Failed to restore from local Datastore due to %v", err)
}
}
@ -272,6 +295,30 @@ func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver,
return nil
}
func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
if !config.IsValidName(name) {
return ErrInvalidName(name)
}
c.Lock()
_, ok := c.ipamDrivers[name]
c.Unlock()
if ok {
return driverapi.ErrActiveRegistration(name)
}
locAS, glbAS, err := driver.GetDefaultAddressSpaces()
if err != nil {
return fmt.Errorf("ipam driver %s failed to return default address spaces: %v", name, err)
}
c.Lock()
c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS}
c.Unlock()
log.Debugf("Registering ipam provider: %s", name)
return nil
}
// NewNetwork creates a new network of the specified network type. The options
// are network specific and modeled in a generic way.
func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) {
@ -292,6 +339,7 @@ func (c *controller) NewNetwork(networkType, name string, options ...NetworkOpti
network := &network{
name: name,
networkType: networkType,
ipamType: ipamapi.DefaultIPAM,
id: stringid.GenerateRandomID(),
ctrlr: c,
endpoints: endpointTable{},
@ -300,14 +348,30 @@ func (c *controller) NewNetwork(networkType, name string, options ...NetworkOpti
network.processOptions(options...)
if err := c.addNetwork(network); err != nil {
if _, err := c.loadNetworkDriver(network); err != nil {
return nil, err
}
if err := c.updateToStore(network); err != nil {
cnfs, err := network.ipamAllocate()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
for _, cn := range cnfs {
cn()
}
}
}()
if err = c.addNetwork(network); err != nil {
return nil, err
}
if err = c.updateToStore(network); err != nil {
log.Warnf("couldnt create network %s: %v", network.name, err)
if e := network.Delete(); e != nil {
log.Warnf("couldnt cleanup network %s: %v", network.name, err)
log.Warnf("couldnt cleanup network %s on network create failure (%v): %v", network.name, err, e)
}
return nil, err
}
@ -316,29 +380,15 @@ func (c *controller) NewNetwork(networkType, name string, options ...NetworkOpti
}
func (c *controller) addNetwork(n *network) error {
c.Lock()
// Check if a driver for the specified network type is available
dd, ok := c.drivers[n.networkType]
c.Unlock()
if !ok {
var err error
dd, err = c.loadDriver(n.networkType)
if err != nil {
return err
}
if _, err := c.loadNetworkDriver(n); err != nil {
return err
}
n.Lock()
n.svcRecords = svcMap{}
n.driver = dd.driver
n.dataScope = dd.capability.DataScope
d := n.driver
n.Unlock()
// Create the network
if err := d.CreateNetwork(n.id, n.generic); err != nil {
if err := d.CreateNetwork(n.id, n.generic, n.getIPv4Data(), n.getIPv6Data()); err != nil {
return err
}
if n.isGlobalScoped() {
@ -534,14 +584,39 @@ func (c *controller) loadDriver(networkType string) (*driverData, error) {
return dd, nil
}
func (c *controller) getDriver(networkType string) (*driverData, error) {
c.Lock()
defer c.Unlock()
dd, ok := c.drivers[networkType]
if !ok {
return nil, types.NotFoundErrorf("driver %s not found", networkType)
func (c *controller) loadIpamDriver(name string) (*ipamData, error) {
if _, err := plugins.Get(name, ipamapi.PluginEndpointType); err != nil {
if err == plugins.ErrNotFound {
return nil, types.NotFoundErrorf(err.Error())
}
return nil, err
}
return dd, nil
c.Lock()
id, ok := c.ipamDrivers[name]
c.Unlock()
if !ok {
return nil, ErrInvalidNetworkDriver(name)
}
return id, nil
}
func (c *controller) getIPAM(name string) (id *ipamData, err error) {
var ok bool
c.Lock()
id, ok = c.ipamDrivers[name]
c.Unlock()
if !ok {
id, err = c.loadIpamDriver(name)
}
return id, err
}
func (c *controller) getIpamDriver(name string) (ipamapi.Ipam, error) {
id, err := c.getIPAM(name)
if err != nil {
return nil, err
}
return id.driver, nil
}
func (c *controller) Stop() {
@ -551,3 +626,24 @@ func (c *controller) Stop() {
c.stopExternalKeyListener()
osl.GC()
}
func (c *controller) loadNetworkDriver(n *network) (driverapi.Driver, error) {
// Check if a driver for the specified network type is available
c.Lock()
dd, ok := c.drivers[n.networkType]
c.Unlock()
if !ok {
var err error
dd, err = c.loadDriver(n.networkType)
if err != nil {
return nil, err
}
}
n.Lock()
n.svcRecords = svcMap{}
n.driver = dd.driver
n.dataScope = dd.capability.DataScope
n.Unlock()
return dd.driver, nil
}

View file

@ -17,7 +17,7 @@ This design ensures that the details of driver registration mechanism are owned
The remote driver implementation uses a `plugins.Client` to communicate with the remote driver process. The `driverapi.Driver` methods are implemented as RPCs over the plugin client.
The payloads of these RPCs are mostly direct translations into JSON of the arguments given to the method. There are some exceptions to account for the use of the interfaces `EndpointInfo` and `JoinInfo`, and data types that do not serialise to JSON well (e.g., `net.IPNet`). The protocol is detailed below under "Protocol".
The payloads of these RPCs are mostly direct translations into JSON of the arguments given to the method. There are some exceptions to account for the use of the interfaces `InterfaceInfo` and `JoinInfo`, and data types that do not serialise to JSON well (e.g., `net.IPNet`). The protocol is detailed below under "Protocol".
## Usage
@ -65,12 +65,42 @@ When the proxy is asked to create a network, the remote process shall receive a
{
"NetworkID": string,
"IPv4Data" : [
{
"AddressSpace": string,
"Pool": ipv4-cidr-string,
"Gateway" : ipv4-address"
"AuxAddresses": {
"<identifier1>" : "<ipv4-address1>",
"<identifier2>" : "<ipv4-address2>",
...
}
},
],
"IPv6Data" : [
{
"AddressSpace": string,
"Pool": ipv6-cidr-string,
"Gateway" : ipv6-address"
"AuxAddresses": {
"<identifier1>" : "<ipv6-address1>",
"<identifier2>" : "<ipv6-address2>",
...
}
},
],
"Options": {
...
}
}
The `NetworkID` value is generated by LibNetwork. The `Options` value is the arbitrary map given to the proxy by LibNetwork.
* `NetworkID` value is generated by LibNetwork which represents an unique network.
* `Options` value is the arbitrary map given to the proxy by LibNetwork.
* `IPv4Data` and `IPv6Data` are the ip-addressing data configured by the user and managed by IPAM driver. The network driver is expected to honor the ip-addressing data supplied by IPAM driver. The data include,
* `AddressSpace` : A unique string represents an isolated space for IP Addressing
* `Pool` : A range of IP Addresses represted in CIDR format address/mask. Since, the IPAM driver is responsible for allocating container ip-addresses, the network driver can make use of this information for the network plumbing purposes.
* `Gateway` : Optionally, the IPAM driver may provide a Gateway for the subnet represented by the Pool. the network driver can make use of this information for the network plumbing purposes.
* `AuxAddresses` : A list of pre-allocated ip-addresses with an associated identifier as provided by the user to assist network driver if it requires specific ip-addresses for its operation.
The response indicating success is empty:

View file

@ -14,7 +14,7 @@ type Driver interface {
// CreateNetwork invokes the driver method to create a network passing
// the network id and network specific config. The config mechanism will
// eventually be replaced with labels which are yet to be introduced.
CreateNetwork(nid string, options map[string]interface{}) error
CreateNetwork(nid string, options map[string]interface{}, ipV4Data, ipV6Data []IPAMData) error
// DeleteNetwork invokes the driver method to delete network passing
// the network id.
@ -25,7 +25,7 @@ type Driver interface {
// specific config. The endpoint information can be either consumed by
// the driver or populated by the driver. The config mechanism will
// eventually be replaced with labels which are yet to be introduced.
CreateEndpoint(nid, eid string, epInfo EndpointInfo, options map[string]interface{}) error
CreateEndpoint(nid, eid string, ifInfo InterfaceInfo, options map[string]interface{}) error
// DeleteEndpoint invokes the driver method to delete an endpoint
// passing the network id and endpoint id.
@ -50,31 +50,26 @@ type Driver interface {
Type() string
}
// EndpointInfo provides a go interface to fetch or populate endpoint assigned network resources.
type EndpointInfo interface {
// Interface returns the interface bound to the endpoint.
// If the value is not nil the driver is only expected to consume the interface.
// It is an error to try to add interface if the passed down value is non-nil
// If the value is nil the driver is expected to add an interface
Interface() InterfaceInfo
// AddInterface is used by the driver to add an interface for the endpoint.
// This method will return an error if the driver attempts to add interface
// if the Interface() method returned a non-nil value.
AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error
}
// InterfaceInfo provides a go interface for drivers to retrive
// network information to interface resources.
type InterfaceInfo interface {
// SetMacAddress allows the driver to set the mac address to the endpoint interface
// during the call to CreateEndpoint, if the mac address is not already set.
SetMacAddress(mac net.HardwareAddr) error
// SetIPAddress allows the driver to set the ip address to the endpoint interface
// during the call to CreateEndpoint, if the address is not already set.
// The API is to be used to assign both the IPv4 and IPv6 address types.
SetIPAddress(ip *net.IPNet) error
// MacAddress returns the MAC address.
MacAddress() net.HardwareAddr
// Address returns the IPv4 address.
Address() net.IPNet
Address() *net.IPNet
// AddressIPv6 returns the IPv6 address.
AddressIPv6() net.IPNet
AddressIPv6() *net.IPNet
}
// InterfaceNameInfo provides a go interface for the drivers to assign names
@ -126,3 +121,13 @@ type NodeDiscoveryData struct {
Address string
Self bool
}
// IPAMData represents the per-network ip related
// operational information libnetwork will send
// to the network driver during CreateNetwork()
type IPAMData struct {
AddressSpace string
Pool *net.IPNet
Gateway *net.IPNet
AuxAddresses map[string]*net.IPNet
}

View file

@ -0,0 +1,119 @@
package driverapi
import (
"encoding/json"
"net"
"testing"
_ "github.com/docker/libnetwork/testutils"
"github.com/docker/libnetwork/types"
)
func TestIPDataMarshalling(t *testing.T) {
i := &IPAMData{
AddressSpace: "giallo",
Pool: &net.IPNet{IP: net.IP{10, 10, 10, 8}, Mask: net.IPMask{255, 255, 255, 0}},
Gateway: &net.IPNet{IP: net.IP{10, 10, 10, 254}, Mask: net.IPMask{255, 255, 255, 0}},
AuxAddresses: map[string]*net.IPNet{
"ip1": &net.IPNet{IP: net.IP{10, 10, 10, 1}, Mask: net.IPMask{255, 255, 255, 0}},
"ip2": &net.IPNet{IP: net.IP{10, 10, 10, 2}, Mask: net.IPMask{255, 255, 255, 0}},
},
}
b, err := json.Marshal(i)
if err != nil {
t.Fatal(err)
}
ii := &IPAMData{}
err = json.Unmarshal(b, &ii)
if err != nil {
t.Fatal(err)
}
if i.AddressSpace != ii.AddressSpace || !types.CompareIPNet(i.Pool, ii.Pool) ||
!types.CompareIPNet(i.Gateway, ii.Gateway) ||
!compareAddresses(i.AuxAddresses, ii.AuxAddresses) {
t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%s\nDecoded:\n%s", i, ii)
}
}
func compareAddresses(a, b map[string]*net.IPNet) bool {
if len(a) != len(b) {
return false
}
if len(a) > 0 {
for k := range a {
if !types.CompareIPNet(a[k], b[k]) {
return false
}
}
}
return true
}
func TestValidateAndIsV6(t *testing.T) {
var err error
i := &IPAMData{
Pool: &net.IPNet{IP: net.IP{10, 10, 10, 8}, Mask: net.IPMask{255, 255, 255, 0}},
Gateway: &net.IPNet{IP: net.IP{10, 10, 10, 254}, Mask: net.IPMask{255, 255, 255, 0}},
AuxAddresses: map[string]*net.IPNet{
"ip1": &net.IPNet{IP: net.IP{10, 10, 10, 1}, Mask: net.IPMask{255, 255, 255, 0}},
"ip2": &net.IPNet{IP: net.IP{10, 10, 10, 2}, Mask: net.IPMask{255, 255, 255, 0}},
},
}
// Check ip version
if i.IsV6() {
t.Fatalf("incorrect ip version returned")
}
orig := i.Pool
if i.Pool, err = types.ParseCIDR("2003::33/64"); err != nil {
t.Fatal(err)
}
if !i.IsV6() {
t.Fatalf("incorrect ip version returned")
}
i.Pool = orig
// valid ip data
if err = i.Validate(); err != nil {
t.Fatal(err)
}
// incongruent gw ver
if i.Gateway, err = types.ParseCIDR("2001::45/65"); err != nil {
t.Fatal(err)
}
if err = i.Validate(); err == nil {
t.Fatalf("expected error but succeded")
}
i.Gateway = nil
// incongruent secondary ip ver
if i.AuxAddresses["ip2"], err = types.ParseCIDR("2002::44/80"); err != nil {
t.Fatal(err)
}
if err = i.Validate(); err == nil {
t.Fatalf("expected error but succeded")
}
delete(i.AuxAddresses, "ip2")
// gw outside pool
if i.Gateway, err = types.ParseCIDR("10.10.15.254/24"); err != nil {
t.Fatal(err)
}
if err = i.Validate(); err == nil {
t.Fatalf("expected error but succeded")
}
i.Gateway = nil
// sec ip outside of pool
if i.AuxAddresses["ip1"], err = types.ParseCIDR("10.10.2.1/24"); err != nil {
t.Fatal(err)
}
if err = i.Validate(); err == nil {
t.Fatalf("expected error but succeded")
}
}

View file

@ -0,0 +1,103 @@
package driverapi
import (
"encoding/json"
"fmt"
"net"
"github.com/docker/libnetwork/types"
)
// MarshalJSON encodes IPAMData into json message
func (i *IPAMData) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{}
m["AddressSpace"] = i.AddressSpace
if i.Pool != nil {
m["Pool"] = i.Pool.String()
}
if i.Gateway != nil {
m["Gateway"] = i.Gateway.String()
}
if i.AuxAddresses != nil {
am := make(map[string]string, len(i.AuxAddresses))
for k, v := range i.AuxAddresses {
am[k] = v.String()
}
m["AuxAddresses"] = am
}
return json.Marshal(m)
}
// UnmarshalJSON decodes a json message into IPAMData
func (i *IPAMData) UnmarshalJSON(data []byte) error {
var (
m map[string]interface{}
err error
)
if err := json.Unmarshal(data, &m); err != nil {
return err
}
i.AddressSpace = m["AddressSpace"].(string)
if v, ok := m["Pool"]; ok {
if i.Pool, err = types.ParseCIDR(v.(string)); err != nil {
return err
}
}
if v, ok := m["Gateway"]; ok {
if i.Gateway, err = types.ParseCIDR(v.(string)); err != nil {
return err
}
}
if v, ok := m["AuxAddresses"]; ok {
b, _ := json.Marshal(v)
var am map[string]string
if err = json.Unmarshal(b, &am); err != nil {
return err
}
i.AuxAddresses = make(map[string]*net.IPNet, len(am))
for k, v := range am {
if i.AuxAddresses[k], err = types.ParseCIDR(v); err != nil {
return err
}
}
}
return nil
}
// Validate checks wheter the IPAMData structure contains congruent data
func (i *IPAMData) Validate() error {
var isV6 bool
if i.Pool == nil {
return types.BadRequestErrorf("invalid pool")
}
if i.Gateway == nil {
return types.BadRequestErrorf("invalid gateway address")
}
isV6 = i.IsV6()
if isV6 && i.Gateway.IP.To4() != nil || !isV6 && i.Gateway.IP.To4() == nil {
return types.BadRequestErrorf("incongruent ip versions for pool and gateway")
}
for k, sip := range i.AuxAddresses {
if isV6 && sip.IP.To4() != nil || !isV6 && sip.IP.To4() == nil {
return types.BadRequestErrorf("incongruent ip versions for pool and secondary ip address %s", k)
}
}
if !i.Pool.Contains(i.Gateway.IP) {
return types.BadRequestErrorf("invalid gateway address (%s) does not belong to the pool (%s)", i.Gateway, i.Pool)
}
for k, sip := range i.AuxAddresses {
if !i.Pool.Contains(sip.IP) {
return types.BadRequestErrorf("invalid secondary address %s (%s) does not belong to the pool (%s)", k, i.Gateway, i.Pool)
}
}
return nil
}
// IsV6 returns wheter this is an IPv6 IPAMData structure
func (i *IPAMData) IsV6() bool {
return nil == i.Pool.IP.To4()
}
func (i *IPAMData) String() string {
return fmt.Sprintf("AddressSpace: %s\nPool: %v\nGateway: %v\nAddresses: %v", i.AddressSpace, i.Pool, i.Gateway, i.AuxAddresses)
}

View file

@ -4,6 +4,9 @@ import (
"strings"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/ipamapi"
builtinIpam "github.com/docker/libnetwork/ipams/builtin"
remoteIpam "github.com/docker/libnetwork/ipams/remote"
"github.com/docker/libnetwork/netlabel"
)
@ -53,3 +56,15 @@ func makeDriverConfig(c *controller, ntype string) map[string]interface{} {
return config
}
func initIpams(ic ipamapi.Callback, lDs, gDs interface{}) error {
for _, fn := range [](func(ipamapi.Callback, interface{}, interface{}) error){
builtinIpam.Init,
remoteIpam.Init,
} {
if err := fn(ic, lDs, gDs); err != nil {
return err
}
}
return nil
}

View file

@ -569,7 +569,7 @@ func (d *driver) getNetworks() []*bridgeNetwork {
}
// Create a new network using bridge plugin
func (d *driver) CreateNetwork(id string, option map[string]interface{}) error {
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
var err error
defer osl.InitOSContext()()
@ -861,7 +861,7 @@ func setHairpinMode(link netlink.Link, enable bool) error {
return nil
}
func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
var (
ipv6Addr *net.IPNet
err error
@ -869,12 +869,8 @@ func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo,
defer osl.InitOSContext()()
if epInfo == nil {
return errors.New("invalid endpoint info passed")
}
if epInfo.Interface() != nil {
return errors.New("non-nil interface passed to bridge(local) driver")
if ifInfo == nil {
return errors.New("invalid interface info passed")
}
// Get the network handler and make sure it exists
@ -1060,17 +1056,27 @@ func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo,
endpoint.addrv6 = ipv6Addr
}
err = epInfo.AddInterface(endpoint.macAddress, *ipv4Addr, *ipv6Addr)
if err != nil {
return err
}
// Program any required port mapping and store them in the endpoint
endpoint.portMapping, err = n.allocatePorts(epConfig, endpoint, config.DefaultBindingIP, d.config.EnableUserlandProxy)
if err != nil {
return err
}
err = ifInfo.SetMacAddress(endpoint.macAddress)
if err != nil {
return err
}
err = ifInfo.SetIPAddress(ipv4Addr)
if err != nil {
return err
}
if config.EnableIPv6 {
err = ifInfo.SetIPAddress(ipv6Addr)
if err != nil {
return err
}
}
return nil
}

View file

@ -51,15 +51,15 @@ func TestCreateFullOptions(t *testing.T) {
netOption := make(map[string]interface{})
netOption[netlabel.GenericData] = netConfig
err := d.CreateNetwork("dummy", netOption)
err := d.CreateNetwork("dummy", netOption, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
// Verify the IP address allocated for the endpoint belongs to the container network
epOptions := make(map[string]interface{})
te := &testEndpoint{}
err = d.CreateEndpoint("dummy", "ep1", te, epOptions)
te := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
@ -77,7 +77,7 @@ func TestCreateNoConfig(t *testing.T) {
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
if err := d.CreateNetwork("dummy", genericOption); err != nil {
if err := d.CreateNetwork("dummy", genericOption, nil, nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
}
@ -94,11 +94,11 @@ func TestCreate(t *testing.T) {
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
if err := d.CreateNetwork("dummy", genericOption); err != nil {
if err := d.CreateNetwork("dummy", genericOption, nil, nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
err := d.CreateNetwork("dummy", genericOption)
err := d.CreateNetwork("dummy", genericOption, nil, nil)
if err == nil {
t.Fatalf("Expected bridge driver to refuse creation of second network with default name")
}
@ -127,7 +127,7 @@ func TestCreateFail(t *testing.T) {
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
if err := d.CreateNetwork("dummy", genericOption); err == nil {
if err := d.CreateNetwork("dummy", genericOption, nil, nil); err == nil {
t.Fatal("Bridge creation was expected to fail")
}
}
@ -149,19 +149,19 @@ func TestCreateMultipleNetworks(t *testing.T) {
config1 := &networkConfiguration{BridgeName: "net_test_1"}
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = config1
if err := d.CreateNetwork("1", genericOption); err != nil {
if err := d.CreateNetwork("1", genericOption, nil, nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
config2 := &networkConfiguration{BridgeName: "net_test_2"}
genericOption[netlabel.GenericData] = config2
if err := d.CreateNetwork("2", genericOption); err != nil {
if err := d.CreateNetwork("2", genericOption, nil, nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
config3 := &networkConfiguration{BridgeName: "net_test_3"}
genericOption[netlabel.GenericData] = config3
if err := d.CreateNetwork("3", genericOption); err != nil {
if err := d.CreateNetwork("3", genericOption, nil, nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
@ -170,7 +170,7 @@ func TestCreateMultipleNetworks(t *testing.T) {
config4 := &networkConfiguration{BridgeName: "net_test_4"}
genericOption[netlabel.GenericData] = config4
if err := d.CreateNetwork("4", genericOption); err != nil {
if err := d.CreateNetwork("4", genericOption, nil, nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
@ -208,8 +208,8 @@ func verifyV4INCEntries(networks map[string]*bridgeNetwork, numEntries int, t *t
type testInterface struct {
mac net.HardwareAddr
addr net.IPNet
addrv6 net.IPNet
addr *net.IPNet
addrv6 *net.IPNet
srcName string
dstName string
}
@ -231,24 +231,47 @@ func (te *testEndpoint) Interface() driverapi.InterfaceInfo {
return nil
}
func (te *testEndpoint) AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
iface := &testInterface{addr: ipv4, addrv6: ipv6}
te.iface = iface
return nil
}
func (i *testInterface) MacAddress() net.HardwareAddr {
return i.mac
}
func (i *testInterface) Address() net.IPNet {
func (i *testInterface) Address() *net.IPNet {
return i.addr
}
func (i *testInterface) AddressIPv6() net.IPNet {
func (i *testInterface) AddressIPv6() *net.IPNet {
return i.addrv6
}
func (i *testInterface) SetMacAddress(mac net.HardwareAddr) error {
if i.mac != nil {
return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", i.mac, mac)
}
if mac == nil {
return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
}
i.mac = types.GetMacCopy(mac)
return nil
}
func (i *testInterface) SetIPAddress(address *net.IPNet) error {
if address.IP == nil {
return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
}
if address.IP.To4() == nil {
return setAddress(&i.addrv6, address)
}
return setAddress(&i.addr, address)
}
func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error {
if *ifaceAddr != nil {
return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
}
*ifaceAddr = types.GetIPNetCopy(address)
return nil
}
func (i *testInterface) SetNames(srcName string, dstName string) error {
i.srcName = srcName
i.dstName = dstName
@ -308,7 +331,7 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
err := d.CreateNetwork("net1", genericOption)
err := d.CreateNetwork("net1", genericOption, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
@ -317,8 +340,8 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
epOptions := make(map[string]interface{})
epOptions[netlabel.PortMap] = portMappings
te := &testEndpoint{}
err = d.CreateEndpoint("net1", "ep1", te, epOptions)
te := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("net1", "ep1", te.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
@ -368,7 +391,7 @@ func TestCreateLinkWithOptions(t *testing.T) {
netOptions := make(map[string]interface{})
netOptions[netlabel.GenericData] = netconfig
err := d.CreateNetwork("net1", netOptions)
err := d.CreateNetwork("net1", netOptions, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
@ -377,8 +400,8 @@ func TestCreateLinkWithOptions(t *testing.T) {
epOptions := make(map[string]interface{})
epOptions[netlabel.MacAddress] = mac
te := &testEndpoint{}
err = d.CreateEndpoint("net1", "ep", te, epOptions)
te := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("net1", "ep", te.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint: %s", err.Error())
}
@ -437,7 +460,7 @@ func TestLinkContainers(t *testing.T) {
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
err := d.CreateNetwork("net1", genericOption)
err := d.CreateNetwork("net1", genericOption, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
@ -446,8 +469,8 @@ func TestLinkContainers(t *testing.T) {
epOptions := make(map[string]interface{})
epOptions[netlabel.ExposedPorts] = exposedPorts
te1 := &testEndpoint{}
err = d.CreateEndpoint("net1", "ep1", te1, epOptions)
te1 := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("net1", "ep1", te1.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
@ -457,8 +480,8 @@ func TestLinkContainers(t *testing.T) {
t.Fatalf("No Ipv4 address assigned to the endpoint: ep1")
}
te2 := &testEndpoint{}
err = d.CreateEndpoint("net1", "ep2", te2, nil)
te2 := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("net1", "ep2", te2.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
@ -668,13 +691,13 @@ func TestSetDefaultGw(t *testing.T) {
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
err := d.CreateNetwork("dummy", genericOption, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{}
err = d.CreateEndpoint("dummy", "ep", te, nil)
te := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create endpoint: %v", err)
}

View file

@ -26,13 +26,13 @@ func TestLinkCreate(t *testing.T) {
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
err := d.CreateNetwork("dummy", genericOption, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{}
err = d.CreateEndpoint("dummy", "", te, nil)
te := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "", te.Interface(), nil)
if err != nil {
if _, ok := err.(InvalidEndpointIDError); !ok {
t.Fatalf("Failed with a wrong error :%s", err.Error())
@ -42,7 +42,7 @@ func TestLinkCreate(t *testing.T) {
}
// Good endpoint creation
err = d.CreateEndpoint("dummy", "ep", te, nil)
err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
@ -64,7 +64,7 @@ func TestLinkCreate(t *testing.T) {
// then we could check the MTU on hostLnk as well.
te1 := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te1, nil)
err = d.CreateEndpoint("dummy", "ep", te1.Interface(), nil)
if err == nil {
t.Fatalf("Failed to detect duplicate endpoint id on same network")
}
@ -117,19 +117,19 @@ func TestLinkCreateTwo(t *testing.T) {
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
err := d.CreateNetwork("dummy", genericOption, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te1 := &testEndpoint{}
err = d.CreateEndpoint("dummy", "ep", te1, nil)
te1 := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te1.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
te2 := &testEndpoint{}
err = d.CreateEndpoint("dummy", "ep", te2, nil)
te2 := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te2.Interface(), nil)
if err != nil {
if _, ok := err.(driverapi.ErrEndpointExists); !ok {
t.Fatalf("Failed with a wrong error: %s", err.Error())
@ -152,19 +152,19 @@ func TestLinkCreateNoEnableIPv6(t *testing.T) {
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
err := d.CreateNetwork("dummy", genericOption, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{}
err = d.CreateEndpoint("dummy", "ep", te, nil)
te := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
iface := te.iface
if iface.addrv6.IP.To16() != nil {
if iface.addrv6 != nil && iface.addrv6.IP.To16() != nil {
t.Fatalf("Expectd IPv6 address to be nil when IPv6 is not enabled. Got IPv6 = %s", iface.addrv6.String())
}
@ -187,13 +187,13 @@ func TestLinkDelete(t *testing.T) {
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption)
err := d.CreateNetwork("dummy", genericOption, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{}
err = d.CreateEndpoint("dummy", "ep1", te, nil)
te := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}

View file

@ -44,13 +44,13 @@ func TestPortMappingConfig(t *testing.T) {
netOptions := make(map[string]interface{})
netOptions[netlabel.GenericData] = netConfig
err := d.CreateNetwork("dummy", netOptions)
err := d.CreateNetwork("dummy", netOptions, nil, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := &testEndpoint{}
err = d.CreateEndpoint("dummy", "ep1", te, epOptions)
te := &testEndpoint{iface: &testInterface{}}
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create the endpoint: %s", err.Error())
}

View file

@ -23,7 +23,7 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
return dc.RegisterDriver(networkType, &driver{}, c)
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}) error {
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
d.Lock()
defer d.Unlock()
@ -40,7 +40,7 @@ func (d *driver) DeleteNetwork(nid string) error {
return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType)
}
func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
return nil
}

View file

@ -14,7 +14,7 @@ func TestDriver(t *testing.T) {
t.Fatalf("Unexpected network type returned by driver")
}
err := d.CreateNetwork("first", nil)
err := d.CreateNetwork("first", nil, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -23,7 +23,7 @@ func TestDriver(t *testing.T) {
t.Fatalf("Unexpected network id stored")
}
err = d.CreateNetwork("second", nil)
err = d.CreateNetwork("second", nil, nil, nil)
if err == nil {
t.Fatalf("Second network creation should fail on this driver")
}

View file

@ -23,7 +23,7 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
return dc.RegisterDriver(networkType, &driver{}, c)
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}) error {
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
d.Lock()
defer d.Unlock()
@ -40,7 +40,7 @@ func (d *driver) DeleteNetwork(nid string) error {
return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType)
}
func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
return nil
}

View file

@ -14,7 +14,7 @@ func TestDriver(t *testing.T) {
t.Fatalf("Unexpected network type returned by driver")
}
err := d.CreateNetwork("first", nil)
err := d.CreateNetwork("first", nil, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -23,7 +23,7 @@ func TestDriver(t *testing.T) {
t.Fatalf("Unexpected network id stored")
}
err = d.CreateNetwork("second", nil)
err = d.CreateNetwork("second", nil, nil, nil)
if err == nil {
t.Fatalf("Second network creation should fail on this driver")
}

View file

@ -25,8 +25,7 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
}
if err := n.joinSandbox(); err != nil {
return fmt.Errorf("network sandbox join failed: %v",
err)
return fmt.Errorf("network sandbox join failed: %v", err)
}
sbox := n.sandbox()
@ -63,7 +62,7 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
}
if err := netlink.LinkSetHardwareAddr(veth, ep.mac); err != nil {
return fmt.Errorf("could not set mac address to the container interface: %v", err)
return fmt.Errorf("could not set mac address (%v) to the container interface: %v", ep.mac, err)
}
if iNames := jinfo.InterfaceName(); iNames != nil {

View file

@ -1,7 +1,6 @@
package overlay
import (
"encoding/binary"
"fmt"
"net"
@ -36,7 +35,7 @@ func (n *network) deleteEndpoint(eid string) {
n.Unlock()
}
func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo,
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
epOptions map[string]interface{}) error {
if err := validateID(nid, eid); err != nil {
return err
@ -48,35 +47,20 @@ func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo,
}
ep := &endpoint{
id: eid,
id: eid,
addr: ifInfo.Address(),
mac: ifInfo.MacAddress(),
}
if epInfo != nil && epInfo.Interface() != nil {
addr := epInfo.Interface().Address()
ep.addr = &addr
ep.mac = epInfo.Interface().MacAddress()
n.addEndpoint(ep)
return nil
if ep.addr == nil {
return fmt.Errorf("create endpoint was not passed interface IP address")
}
ipID, err := d.ipAllocator.GetID()
if err != nil {
return fmt.Errorf("could not allocate ip from subnet %s: %v",
bridgeSubnet.String(), err)
}
ep.addr = &net.IPNet{
Mask: bridgeSubnet.Mask,
}
ep.addr.IP = make([]byte, 4)
binary.BigEndian.PutUint32(ep.addr.IP, bridgeSubnetInt+ipID)
ep.mac = netutils.GenerateMACFromIP(ep.addr.IP)
err = epInfo.AddInterface(ep.mac, *ep.addr, net.IPNet{})
if err != nil {
return fmt.Errorf("could not add interface to endpoint info: %v", err)
if ep.mac == nil {
ep.mac = netutils.GenerateMACFromIP(ep.addr.IP)
if err := ifInfo.SetMacAddress(ep.mac); err != nil {
return err
}
}
n.addEndpoint(ep)
@ -99,7 +83,6 @@ func (d *driver) DeleteEndpoint(nid, eid string) error {
return fmt.Errorf("endpoint id %q not found", eid)
}
d.ipAllocator.Release(binary.BigEndian.Uint32(ep.addr.IP) - bridgeSubnetInt)
n.deleteEndpoint(eid)
return nil
}

View file

@ -9,7 +9,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/ipallocator"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/osl"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
@ -18,24 +18,24 @@ import (
type networkTable map[string]*network
type network struct {
id string
vni uint32
dbIndex uint64
dbExists bool
sbox osl.Sandbox
endpoints endpointTable
ipAllocator *ipallocator.IPAllocator
gw net.IP
vxlanName string
driver *driver
joinCnt int
once *sync.Once
initEpoch int
initErr error
id string
vni uint32
dbIndex uint64
dbExists bool
sbox osl.Sandbox
endpoints endpointTable
vxlanName string
driver *driver
joinCnt int
once *sync.Once
initEpoch int
initErr error
subnets []*net.IPNet
gateways []*net.IPNet
sync.Mutex
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}) error {
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
if id == "" {
return fmt.Errorf("invalid network id")
}
@ -51,7 +51,13 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}) error {
once: &sync.Once{},
}
n.gw = bridgeIP.IP
n.subnets = make([]*net.IPNet, len(ipV4Data))
n.gateways = make([]*net.IPNet, len(ipV4Data))
for i, ipd := range ipV4Data {
n.subnets[i] = ipd.Pool
n.gateways[i] = ipd.Gateway
}
d.addNetwork(n)
@ -151,7 +157,7 @@ func (n *network) initSandbox() error {
// Add a bridge inside the namespace
if err := sbox.AddInterface("bridge1", "br",
sbox.InterfaceOptions().Address(bridgeIP),
sbox.InterfaceOptions().Address(n.gateways[0]),
sbox.InterfaceOptions().Bridge(true)); err != nil {
return fmt.Errorf("could not create bridge inside the network sandbox: %v", err)
}

View file

@ -1,9 +1,7 @@
package overlay
import (
"encoding/binary"
"fmt"
"net"
"sync"
"github.com/Sirupsen/logrus"
@ -44,36 +42,8 @@ type driver struct {
sync.Mutex
}
var (
bridgeSubnet, bridgeIP *net.IPNet
once sync.Once
bridgeSubnetInt uint32
)
func onceInit() {
var err error
_, bridgeSubnet, err = net.ParseCIDR("172.21.0.0/16")
if err != nil {
panic("could not parse cid 172.21.0.0/16")
}
bridgeSubnetInt = binary.BigEndian.Uint32(bridgeSubnet.IP.To4())
ip, subnet, err := net.ParseCIDR("172.21.255.254/16")
if err != nil {
panic("could not parse cid 172.21.255.254/16")
}
bridgeIP = &net.IPNet{
IP: ip,
Mask: subnet.Mask,
}
}
// Init registers a new instance of overlay driver
func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
once.Do(onceInit)
c := driverapi.Capability{
DataScope: datastore.GlobalScope,
}

View file

@ -34,6 +34,9 @@ type CreateNetworkRequest struct {
// A free form map->object interface for communication of options.
Options map[string]interface{}
// IPAMData contains the address pool information for this network
IPv4Data, IPv6Data []driverapi.IPAMData
}
// CreateNetworkResponse is the response to the CreateNetworkRequest.

View file

@ -82,10 +82,12 @@ func (d *driver) call(methodName string, arg interface{}, retVal maybeError) err
return nil
}
func (d *driver) CreateNetwork(id string, options map[string]interface{}) error {
func (d *driver) CreateNetwork(id string, options map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
create := &api.CreateNetworkRequest{
NetworkID: id,
Options: options,
IPv4Data: ipV4Data,
IPv6Data: ipV6Data,
}
return d.call("CreateNetwork", create, &api.CreateNetworkResponse{})
}
@ -95,23 +97,22 @@ func (d *driver) DeleteNetwork(nid string) error {
return d.call("DeleteNetwork", delete, &api.DeleteNetworkResponse{})
}
func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
var reqIface *api.EndpointInterface
if epInfo == nil {
return fmt.Errorf("must not be called with nil EndpointInfo")
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
if ifInfo == nil {
return fmt.Errorf("must not be called with nil InterfaceInfo")
}
iface := epInfo.Interface()
if iface != nil {
addr4 := iface.Address()
addr6 := iface.AddressIPv6()
reqIface = &api.EndpointInterface{
Address: addr4.String(),
AddressIPv6: addr6.String(),
MacAddress: iface.MacAddress().String(),
}
reqIface := &api.EndpointInterface{}
if ifInfo.Address() != nil {
reqIface.Address = ifInfo.Address().String()
}
if ifInfo.AddressIPv6() != nil {
reqIface.AddressIPv6 = ifInfo.AddressIPv6().String()
}
if ifInfo.MacAddress() != nil {
reqIface.MacAddress = ifInfo.MacAddress().String()
}
create := &api.CreateEndpointRequest{
NetworkID: nid,
EndpointID: eid,
@ -127,24 +128,27 @@ func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo,
if err != nil {
return err
}
if reqIface != nil && inIface != nil {
// We're not supposed to add interface if there is already
// one. Attempt to roll back
return errorWithRollback("driver attempted to add interface ignoring the one provided", d.DeleteEndpoint(nid, eid))
if inIface == nil {
// Remote driver did not set any field
return nil
}
if inIface != nil {
var addr4, addr6 net.IPNet
if inIface.Address != nil {
addr4 = *(inIface.Address)
}
if inIface.AddressIPv6 != nil {
addr6 = *(inIface.AddressIPv6)
}
if err := epInfo.AddInterface(inIface.MacAddress, addr4, addr6); err != nil {
return errorWithRollback(fmt.Sprintf("failed to AddInterface %v: %s", inIface, err), d.DeleteEndpoint(nid, eid))
if inIface.MacAddress != nil {
if err := ifInfo.SetMacAddress(inIface.MacAddress); err != nil {
return errorWithRollback(fmt.Sprintf("driver modified interface MAC address: %v", err), d.DeleteEndpoint(nid, eid))
}
}
if inIface.Address != nil {
if err := ifInfo.SetIPAddress(inIface.Address); err != nil {
return errorWithRollback(fmt.Sprintf("driver modified interface address: %v", err), d.DeleteEndpoint(nid, eid))
}
}
if inIface.AddressIPv6 != nil {
if err := ifInfo.SetIPAddress(inIface.AddressIPv6); err != nil {
return errorWithRollback(fmt.Sprintf("driver modified interface address: %v", err), d.DeleteEndpoint(nid, eid))
}
}
return nil
}
@ -193,11 +197,7 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
}
ifaceName := res.InterfaceName
if jinfo.InterfaceName() != nil && ifaceName == nil {
return fmt.Errorf("no interface name information received while one is expected")
}
if iface := jinfo.InterfaceName(); iface != nil {
if iface := jinfo.InterfaceName(); iface != nil && ifaceName != nil {
if err := iface.SetNames(ifaceName.SrcName, ifaceName.DstPrefix); err != nil {
return errorWithRollback(fmt.Sprintf("failed to set interface name: %s", err), d.Leave(nid, eid))
}
@ -278,7 +278,7 @@ func parseStaticRoutes(r api.JoinResponse) ([]*types.StaticRoute, error) {
outRoute := &types.StaticRoute{RouteType: inRoute.RouteType}
if inRoute.Destination != "" {
if outRoute.Destination, err = toAddr(inRoute.Destination); err != nil {
if outRoute.Destination, err = types.ParseCIDR(inRoute.Destination); err != nil {
return nil, err
}
}
@ -304,12 +304,12 @@ func parseInterface(r api.CreateEndpointResponse) (*api.Interface, error) {
var err error
outIf = &api.Interface{}
if inIf.Address != "" {
if outIf.Address, err = toAddr(inIf.Address); err != nil {
if outIf.Address, err = types.ParseCIDR(inIf.Address); err != nil {
return nil, err
}
}
if inIf.AddressIPv6 != "" {
if outIf.AddressIPv6, err = toAddr(inIf.AddressIPv6); err != nil {
if outIf.AddressIPv6, err = types.ParseCIDR(inIf.AddressIPv6); err != nil {
return nil, err
}
}
@ -322,12 +322,3 @@ func parseInterface(r api.CreateEndpointResponse) (*api.Interface, error) {
return outIf, nil
}
func toAddr(ipAddr string) (*net.IPNet, error) {
ip, ipnet, err := net.ParseCIDR(ipAddr)
if err != nil {
return nil, err
}
ipnet.IP = ip
return ipnet, nil
}

View file

@ -80,27 +80,59 @@ type testEndpoint struct {
}
func (test *testEndpoint) Interface() driverapi.InterfaceInfo {
return test
}
func (test *testEndpoint) Address() *net.IPNet {
if test.address == "" {
return nil
}
nw, _ := types.ParseCIDR(test.address)
return nw
}
func (test *testEndpoint) AddressIPv6() *net.IPNet {
if test.addressIPv6 == "" {
return nil
}
nw, _ := types.ParseCIDR(test.addressIPv6)
return nw
}
func (test *testEndpoint) MacAddress() net.HardwareAddr {
if test.macAddress == "" {
return nil
}
mac, _ := net.ParseMAC(test.macAddress)
return mac
}
func (test *testEndpoint) SetMacAddress(mac net.HardwareAddr) error {
if test.macAddress != "" {
return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", test.macAddress, mac)
}
if mac == nil {
return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
}
test.macAddress = mac.String()
return nil
}
func (test *testEndpoint) AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
ip4, net4, _ := net.ParseCIDR(test.address)
ip6, net6, _ := net.ParseCIDR(test.addressIPv6)
if ip4 != nil {
net4.IP = ip4
if !types.CompareIPNet(net4, &ipv4) {
test.t.Fatalf("Wrong address given %+v", ipv4)
}
func (test *testEndpoint) SetIPAddress(address *net.IPNet) error {
if address.IP == nil {
return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
}
if ip6 != nil {
net6.IP = ip6
if !types.CompareIPNet(net6, &ipv6) {
test.t.Fatalf("Wrong address (IPv6) given %+v", ipv6)
}
if address.IP.To4() == nil {
return setAddress(&test.addressIPv6, address)
}
if test.macAddress != "" && mac.String() != test.macAddress {
test.t.Fatalf("Wrong MAC address given %v", mac)
return setAddress(&test.address, address)
}
func setAddress(ifaceAddr *string, address *net.IPNet) error {
if *ifaceAddr != "" {
return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
}
*ifaceAddr = address.String()
return nil
}
@ -253,7 +285,7 @@ func TestRemoteDriver(t *testing.T) {
dst: "vethdst",
address: "192.168.5.7/16",
addressIPv6: "2001:DB8::5:7/48",
macAddress: "7a:56:78:34:12:da",
macAddress: "",
gateway: "192.168.0.1",
gatewayIPv6: "2001:DB8::1",
hostsPath: "/here/comes/the/host/path",
@ -289,9 +321,7 @@ func TestRemoteDriver(t *testing.T) {
})
handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
iface := map[string]interface{}{
"Address": ep.address,
"AddressIPv6": ep.addressIPv6,
"MacAddress": ep.macAddress,
"MacAddress": ep.macAddress,
}
return map[string]interface{}{
"Interface": iface,
@ -360,7 +390,7 @@ func TestRemoteDriver(t *testing.T) {
}
netID := "dummy-network"
err = d.CreateNetwork(netID, map[string]interface{}{})
err = d.CreateNetwork(netID, map[string]interface{}{}, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -400,19 +430,6 @@ func TestRemoteDriver(t *testing.T) {
}
}
type failEndpoint struct {
t *testing.T
}
func (f *failEndpoint) Interfaces() []*driverapi.InterfaceInfo {
f.t.Fatal("Unexpected call of Interfaces")
return nil
}
func (f *failEndpoint) AddInterface(int, net.HardwareAddr, net.IPNet, net.IPNet) error {
f.t.Fatal("Unexpected call of AddInterface")
return nil
}
func TestDriverError(t *testing.T) {
var plugin = "test-net-driver-error"
@ -454,7 +471,7 @@ func TestMissingValues(t *testing.T) {
"MacAddress": ep.macAddress,
}
return map[string]interface{}{
"Interfaces": []interface{}{iface},
"Interface": iface,
}
})
@ -473,11 +490,27 @@ type rollbackEndpoint struct {
}
func (r *rollbackEndpoint) Interface() driverapi.InterfaceInfo {
return r
}
func (r *rollbackEndpoint) MacAddress() net.HardwareAddr {
return nil
}
func (r *rollbackEndpoint) AddInterface(_ net.HardwareAddr, _ net.IPNet, _ net.IPNet) error {
return fmt.Errorf("fail this to trigger a rollback")
func (r *rollbackEndpoint) Address() *net.IPNet {
return nil
}
func (r *rollbackEndpoint) AddressIPv6() *net.IPNet {
return nil
}
func (r *rollbackEndpoint) SetMacAddress(mac net.HardwareAddr) error {
return fmt.Errorf("invalid mac")
}
func (r *rollbackEndpoint) SetIPAddress(ip *net.IPNet) error {
return fmt.Errorf("invalid ip")
}
func TestRollback(t *testing.T) {
@ -511,7 +544,7 @@ func TestRollback(t *testing.T) {
ep := &rollbackEndpoint{}
if err := driver.CreateEndpoint("dummy", "dummy", ep, map[string]interface{}{}); err == nil {
if err := driver.CreateEndpoint("dummy", "dummy", ep.Interface(), map[string]interface{}{}); err == nil {
t.Fatalf("Expected error from driver")
}
if !rolledback {

View file

@ -19,7 +19,7 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
return dc.RegisterDriver(networkType, &driver{}, c)
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}) error {
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
return nil
}
@ -27,7 +27,7 @@ func (d *driver) DeleteNetwork(nid string) error {
return nil
}
func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error {
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
return nil
}

View file

@ -10,6 +10,7 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/types"
)
@ -71,7 +72,9 @@ func (ep *endpoint) MarshalJSON() ([]byte, error) {
epMap["id"] = ep.id
epMap["ep_iface"] = ep.iface
epMap["exposed_ports"] = ep.exposedPorts
epMap["generic"] = ep.generic
if ep.generic != nil {
epMap["generic"] = ep.generic
}
epMap["sandbox"] = ep.sandboxID
return json.Marshal(epMap)
}
@ -98,8 +101,8 @@ func (ep *endpoint) UnmarshalJSON(b []byte) (err error) {
cb, _ := json.Marshal(epMap["sandbox"])
json.Unmarshal(cb, &ep.sandboxID)
if epMap["generic"] != nil {
ep.generic = epMap["generic"].(map[string]interface{})
if v, ok := epMap["generic"]; ok {
ep.generic = v.(map[string]interface{})
}
return nil
}
@ -423,6 +426,8 @@ func (ep *endpoint) Delete() error {
return err
}
ep.releaseAddress()
return nil
}
@ -482,7 +487,7 @@ func (ep *endpoint) getFirstInterfaceAddress() net.IP {
ep.Lock()
defer ep.Unlock()
if ep.iface != nil {
if ep.iface.addr != nil {
return ep.iface.addr.IP
}
@ -549,3 +554,48 @@ func (ep *endpoint) DataScope() datastore.DataScope {
func (ep *endpoint) isLocalScoped() bool {
return ep.DataScope() == datastore.LocalScope
}
func (ep *endpoint) assignAddress() error {
var (
ipam ipamapi.Ipam
err error
)
n := ep.getNetwork()
if n.Type() == "host" || n.Type() == "null" || n.Type() == "bridge" {
return nil
}
ipam, err = n.getController().getIpamDriver(n.ipamType)
if err != nil {
return err
}
for _, d := range n.getIPInfo() {
var addr *net.IPNet
addr, _, err = ipam.RequestAddress(d.PoolID, nil, nil)
if err == nil {
ep.Lock()
ep.iface.addr = addr
ep.iface.poolID = d.PoolID
ep.Unlock()
return nil
}
if err != ipamapi.ErrNoAvailableIPs {
return err
}
}
return fmt.Errorf("no available ip addresses on this network address pools: %s (%s)", n.Name(), n.ID())
}
func (ep *endpoint) releaseAddress() {
n := ep.getNetwork()
if n.Type() == "host" || n.Type() == "null" || n.Type() == "bridge" {
return
}
ipam, err := n.getController().getIpamDriver(n.ipamType)
if err != nil {
log.Warnf("Failed to retrieve ipam driver to release interface address on delete of endpoint %s (%s): %v", ep.Name(), ep.ID(), err)
return
}
if err := ipam.ReleaseAddress(ep.iface.poolID, ep.iface.addr.IP); err != nil {
log.Warnf("Failed to release ip address %s on delete of endpoint %s (%s): %v", ep.iface.addr.IP, ep.Name(), ep.ID(), err)
}
}

View file

@ -34,26 +34,33 @@ type InterfaceInfo interface {
MacAddress() net.HardwareAddr
// Address returns the IPv4 address assigned to the endpoint.
Address() net.IPNet
Address() *net.IPNet
// AddressIPv6 returns the IPv6 address assigned to the endpoint.
AddressIPv6() net.IPNet
AddressIPv6() *net.IPNet
}
type endpointInterface struct {
mac net.HardwareAddr
addr net.IPNet
addrv6 net.IPNet
addr *net.IPNet
addrv6 *net.IPNet
srcName string
dstPrefix string
routes []*net.IPNet
poolID string
}
func (epi *endpointInterface) MarshalJSON() ([]byte, error) {
epMap := make(map[string]interface{})
epMap["mac"] = epi.mac.String()
epMap["addr"] = epi.addr.String()
epMap["addrv6"] = epi.addrv6.String()
if epi.mac != nil {
epMap["mac"] = epi.mac.String()
}
if epi.addr != nil {
epMap["addr"] = epi.addr.String()
}
if epi.addrv6 != nil {
epMap["addrv6"] = epi.addrv6.String()
}
epMap["srcName"] = epi.srcName
epMap["dstPrefix"] = epi.dstPrefix
var routes []string
@ -61,28 +68,32 @@ func (epi *endpointInterface) MarshalJSON() ([]byte, error) {
routes = append(routes, route.String())
}
epMap["routes"] = routes
epMap["poolID"] = epi.poolID
return json.Marshal(epMap)
}
func (epi *endpointInterface) UnmarshalJSON(b []byte) (err error) {
var epMap map[string]interface{}
if err := json.Unmarshal(b, &epMap); err != nil {
func (epi *endpointInterface) UnmarshalJSON(b []byte) error {
var (
err error
epMap map[string]interface{}
)
if err = json.Unmarshal(b, &epMap); err != nil {
return err
}
mac, _ := net.ParseMAC(epMap["mac"].(string))
epi.mac = mac
ip, ipnet, _ := net.ParseCIDR(epMap["addr"].(string))
if ipnet != nil {
ipnet.IP = ip
epi.addr = *ipnet
if v, ok := epMap["mac"]; ok {
if epi.mac, err = net.ParseMAC(v.(string)); err != nil {
return types.InternalErrorf("failed to decode endpoint interface mac address after json unmarshal: %s", v.(string))
}
}
ip, ipnet, _ = net.ParseCIDR(epMap["addrv6"].(string))
if ipnet != nil {
ipnet.IP = ip
epi.addrv6 = *ipnet
if v, ok := epMap["addr"]; ok {
if epi.addr, err = types.ParseCIDR(v.(string)); err != nil {
return types.InternalErrorf("failed to decode endpoint interface ipv4 address after json unmarshal: %v", err)
}
}
if v, ok := epMap["addrv6"]; ok {
if epi.addrv6, err = types.ParseCIDR(v.(string)); err != nil {
return types.InternalErrorf("failed to decode endpoint interface ipv6 address after json unmarshal: %v", err)
}
}
epi.srcName = epMap["srcName"].(string)
@ -99,6 +110,7 @@ func (epi *endpointInterface) UnmarshalJSON(b []byte) (err error) {
epi.routes = append(epi.routes, ipr)
}
}
epi.poolID = epMap["poolID"].(string)
return nil
}
@ -149,17 +161,32 @@ func (ep *endpoint) Interface() driverapi.InterfaceInfo {
return nil
}
func (ep *endpoint) AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error {
ep.Lock()
defer ep.Unlock()
iface := &endpointInterface{
addr: *types.GetIPNetCopy(&ipv4),
addrv6: *types.GetIPNetCopy(&ipv6),
func (epi *endpointInterface) SetMacAddress(mac net.HardwareAddr) error {
if epi.mac != nil {
return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", epi.mac, mac)
}
iface.mac = types.GetMacCopy(mac)
if mac == nil {
return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
}
epi.mac = types.GetMacCopy(mac)
return nil
}
ep.iface = iface
func (epi *endpointInterface) SetIPAddress(address *net.IPNet) error {
if address.IP == nil {
return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
}
if address.IP.To4() == nil {
return setAddress(&epi.addrv6, address)
}
return setAddress(&epi.addr, address)
}
func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error {
if *ifaceAddr != nil {
return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
}
*ifaceAddr = types.GetIPNetCopy(address)
return nil
}
@ -167,12 +194,12 @@ func (epi *endpointInterface) MacAddress() net.HardwareAddr {
return types.GetMacCopy(epi.mac)
}
func (epi *endpointInterface) Address() net.IPNet {
return (*types.GetIPNetCopy(&epi.addr))
func (epi *endpointInterface) Address() *net.IPNet {
return types.GetIPNetCopy(epi.addr)
}
func (epi *endpointInterface) AddressIPv6() net.IPNet {
return (*types.GetIPNetCopy(&epi.addrv6))
func (epi *endpointInterface) AddressIPv6() *net.IPNet {
return types.GetIPNetCopy(epi.addrv6)
}
func (epi *endpointInterface) SetNames(srcName string, dstPrefix string) error {

File diff suppressed because it is too large Load diff

View file

@ -7,10 +7,15 @@ import (
"testing"
"time"
"encoding/json"
"github.com/docker/libnetwork/bitseq"
"github.com/docker/libnetwork/config"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/netutils"
_ "github.com/docker/libnetwork/testutils"
"github.com/docker/libnetwork/types"
)
var ds datastore.DataStore
@ -26,13 +31,16 @@ func testMain(m *testing.M) {
os.Exit(m.Run())
}
func getAllocator(t *testing.T, subnet *net.IPNet) *Allocator {
a, err := NewAllocator(ds)
func getAllocator(t *testing.T, subnet string) (*Allocator, string) {
a, err := NewAllocator(nil, ds)
if err != nil {
t.Fatal(err)
}
a.AddSubnet("default", &SubnetInfo{Subnet: subnet})
return a
poolID, _, _, err := a.RequestPool("default", subnet, "", nil, false)
if err != nil {
t.Fatal(err)
}
return a, poolID
}
func TestInt2IP2IntConversion(t *testing.T) {
@ -63,23 +71,23 @@ func TestGetAddressVersion(t *testing.T) {
func TestKeyString(t *testing.T) {
k := &subnetKey{addressSpace: "default", subnet: "172.27.0.0/16"}
k := &SubnetKey{AddressSpace: "default", Subnet: "172.27.0.0/16"}
expected := "default/172.27.0.0/16"
if expected != k.String() {
t.Fatalf("Unexpected key string: %s", k.String())
}
k2 := &subnetKey{}
k2 := &SubnetKey{}
err := k2.FromString(expected)
if err != nil {
t.Fatal(err)
}
if k2.addressSpace != k.addressSpace || k2.subnet != k.subnet {
t.Fatalf("subnetKey.FromString() failed. Expected %v. Got %v", k, k2)
if k2.AddressSpace != k.AddressSpace || k2.Subnet != k.Subnet {
t.Fatalf("SubnetKey.FromString() failed. Expected %v. Got %v", k, k2)
}
expected = fmt.Sprintf("%s/%s", expected, "172.27.3.0/24")
k.childSubnet = "172.27.3.0/24"
k.ChildSubnet = "172.27.3.0/24"
if expected != k.String() {
t.Fatalf("Unexpected key string: %s", k.String())
}
@ -88,64 +96,328 @@ func TestKeyString(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if k2.addressSpace != k.addressSpace || k2.subnet != k.subnet {
t.Fatalf("subnetKey.FromString() failed. Expected %v. Got %v", k, k2)
if k2.AddressSpace != k.AddressSpace || k2.Subnet != k.Subnet || k2.ChildSubnet != k.ChildSubnet {
t.Fatalf("SubnetKey.FromString() failed. Expected %v. Got %v", k, k2)
}
}
func TestAddSubnets(t *testing.T) {
a, err := NewAllocator(nil)
func TestPoolDataMarshal(t *testing.T) {
_, nw, err := net.ParseCIDR("172.28.30.1/24")
if err != nil {
t.Fatal(err)
}
_, sub0, _ := net.ParseCIDR("10.0.0.0/8")
err = a.AddSubnet("default", &SubnetInfo{Subnet: sub0})
p := &PoolData{
ParentKey: SubnetKey{AddressSpace: "Blue", Subnet: "172.28.0.0/16"},
Pool: nw,
Range: &AddressRange{Sub: &net.IPNet{IP: net.IP{172, 28, 20, 0}, Mask: net.IPMask{255, 255, 255, 0}}, Start: 0, End: 255},
RefCount: 4,
}
ba, err := json.Marshal(p)
if err != nil {
t.Fatalf("Unexpected failure in adding subent")
t.Fatal(err)
}
err = a.AddSubnet("abc", &SubnetInfo{Subnet: sub0})
var q PoolData
err = json.Unmarshal(ba, &q)
if err != nil {
t.Fatalf("Unexpected failure in adding overlapping subents to different address spaces")
t.Fatal(err)
}
err = a.AddSubnet("abc", &SubnetInfo{Subnet: sub0})
if err == nil {
t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub0, sub0)
if p.ParentKey != q.ParentKey || !types.CompareIPNet(p.Range.Sub, q.Range.Sub) ||
p.Range.Start != q.Range.Start || p.Range.End != q.Range.End || p.RefCount != q.RefCount ||
!types.CompareIPNet(p.Pool, q.Pool) {
t.Fatalf("\n%#v\n%#v", p, &q)
}
_, sub1, _ := net.ParseCIDR("10.20.2.0/24")
err = a.AddSubnet("default", &SubnetInfo{Subnet: sub1})
if err == nil {
t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub0, sub1)
p = &PoolData{
ParentKey: SubnetKey{AddressSpace: "Blue", Subnet: "172.28.0.0/16"},
Pool: nw,
RefCount: 4,
}
_, sub2, _ := net.ParseCIDR("10.128.0.0/9")
err = a.AddSubnet("default", &SubnetInfo{Subnet: sub2})
if err == nil {
t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub1, sub2)
}
_, sub6, err := net.ParseCIDR("1003:1:2:3:4:5:6::/112")
ba, err = json.Marshal(p)
if err != nil {
t.Fatalf("Wrong input, Can't proceed: %s", err.Error())
t.Fatal(err)
}
err = a.AddSubnet("default", &SubnetInfo{Subnet: sub6})
err = json.Unmarshal(ba, &q)
if err != nil {
t.Fatal(err)
}
if q.Range != nil {
t.Fatalf("Unexpected Range")
}
}
func TestSubnetsMarshal(t *testing.T) {
a, _ := NewAllocator(nil, nil)
pid0, _, _, err := a.RequestPool(localAddressSpace, "192.168.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
pid1, _, _, err := a.RequestPool(localAddressSpace, "192.169.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
_, _, err = a.RequestAddress(pid0, nil, nil)
if err != nil {
t.Fatal(err)
}
ba := a.Value()
a.subnets = make(map[SubnetKey]*PoolData, 0)
if err := a.SetValue(ba); err != nil {
t.Fatal(err)
}
expIP := &net.IPNet{IP: net.IP{192, 168, 0, 2}, Mask: net.IPMask{255, 255, 0, 0}}
ip, _, err := a.RequestAddress(pid0, nil, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(expIP, ip) {
t.Fatalf("Got unexpected ip after pool config restore: %s", ip)
}
expIP = &net.IPNet{IP: net.IP{192, 169, 0, 1}, Mask: net.IPMask{255, 255, 0, 0}}
ip, _, err = a.RequestAddress(pid1, nil, nil)
if err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(expIP, ip) {
t.Fatalf("Got unexpected ip after pool config restore: %s", ip)
}
}
func TestAddSubnets(t *testing.T) {
a, err := NewAllocator(nil, nil)
if err != nil {
t.Fatal(err)
}
pid0, _, _, err := a.RequestPool("default", "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatalf("Unexpected failure in adding subnet")
}
pid1, _, _, err := a.RequestPool("abc", "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatalf("Unexpected failure in adding overlapping subnets to different address spaces: %v", err)
}
if pid0 == pid1 {
t.Fatalf("returned same pool id for same subnets in different namespaces")
}
pid, _, _, err := a.RequestPool("abc", "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatalf("Unexpected failure requesting existing subnet: %v", err)
}
if pid != pid1 {
t.Fatalf("returned different pool id for same subnet requests")
}
_, _, _, err = a.RequestPool("abc", "10.128.0.0/9", "", nil, false)
if err == nil {
t.Fatalf("Expected failure on adding overlapping base subnet")
}
pid2, _, _, err := a.RequestPool("abc", "10.0.0.0/8", "10.128.0.0/9", nil, false)
if err != nil {
t.Fatalf("Unexpected failure on adding sub pool: %v", err)
}
pid3, _, _, err := a.RequestPool("abc", "10.0.0.0/8", "10.128.0.0/9", nil, false)
if err != nil {
t.Fatalf("Unexpected failure on adding overlapping sub pool: %v", err)
}
if pid2 != pid3 {
t.Fatalf("returned different pool id for same sub pool requests")
}
pid, _, _, err = a.RequestPool("default", "10.20.2.0/24", "", nil, false)
if err == nil {
t.Fatalf("Failed to detect overlapping subnets")
}
_, _, _, err = a.RequestPool("default", "10.128.0.0/9", "", nil, false)
if err == nil {
t.Fatalf("Failed to detect overlapping subnets")
}
_, _, _, err = a.RequestPool("default", "1003:1:2:3:4:5:6::/112", "", nil, false)
if err != nil {
t.Fatalf("Failed to add v6 subnet: %s", err.Error())
}
_, sub6, err = net.ParseCIDR("1003:1:2:3::/64")
if err != nil {
t.Fatalf("Wrong input, Can't proceed: %s", err.Error())
}
err = a.AddSubnet("default", &SubnetInfo{Subnet: sub6})
_, _, _, err = a.RequestPool("default", "1003:1:2:3::/64", "", nil, false)
if err == nil {
t.Fatalf("Failed to detect overlapping v6 subnet")
}
}
func TestAddReleasePoolID(t *testing.T) {
var k0, k1, k2 SubnetKey
a, err := NewAllocator(nil, nil)
if err != nil {
t.Fatal(err)
}
pid0, _, _, err := a.RequestPool("default", "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatalf("Unexpected failure in adding pool")
}
if err := k0.FromString(pid0); err != nil {
t.Fatal(err)
}
if a.subnets[k0].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k0, a.subnets[k0].RefCount)
}
pid1, _, _, err := a.RequestPool("default", "10.0.0.0/8", "10.0.0.0/16", nil, false)
if err != nil {
t.Fatalf("Unexpected failure in adding sub pool")
}
if err := k1.FromString(pid1); err != nil {
t.Fatal(err)
}
if a.subnets[k1].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k1, a.subnets[k1].RefCount)
}
pid2, _, _, err := a.RequestPool("default", "10.0.0.0/8", "10.0.0.0/16", nil, false)
if err != nil {
t.Fatalf("Unexpected failure in adding sub pool")
}
if pid0 == pid1 || pid0 == pid2 || pid1 != pid2 {
t.Fatalf("Incorrect poolIDs returned %s, %s, %s", pid0, pid1, pid2)
}
if err := k2.FromString(pid2); err != nil {
t.Fatal(err)
}
if a.subnets[k2].RefCount != 2 {
t.Fatalf("Unexpected ref count for %s: %d", k2, a.subnets[k2].RefCount)
}
if a.subnets[k0].RefCount != 3 {
t.Fatalf("Unexpected ref count for %s: %d", k0, a.subnets[k0].RefCount)
}
if err := a.ReleasePool(pid1); err != nil {
t.Fatal(err)
}
if a.subnets[k0].RefCount != 2 {
t.Fatalf("Unexpected ref count for %s: %d", k0, a.subnets[k0].RefCount)
}
if err := a.ReleasePool(pid0); err != nil {
t.Fatal(err)
}
if a.subnets[k0].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k0, a.subnets[k0].RefCount)
}
pid00, _, _, err := a.RequestPool("default", "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatalf("Unexpected failure in adding pool")
}
if pid00 != pid0 {
t.Fatalf("main pool should still exist")
}
if a.subnets[k0].RefCount != 2 {
t.Fatalf("Unexpected ref count for %s: %d", k0, a.subnets[k0].RefCount)
}
if err := a.ReleasePool(pid2); err != nil {
t.Fatal(err)
}
if a.subnets[k0].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k0, a.subnets[k0].RefCount)
}
if err := a.ReleasePool(pid00); err != nil {
t.Fatal(err)
}
if bp, ok := a.subnets[k0]; ok {
t.Fatalf("Base pool %s is still present: %v", k0, bp)
}
_, _, _, err = a.RequestPool("default", "10.0.0.0/8", "", nil, false)
if err != nil {
t.Fatalf("Unexpected failure in adding pool")
}
if a.subnets[k0].RefCount != 1 {
t.Fatalf("Unexpected ref count for %s: %d", k0, a.subnets[k0].RefCount)
}
}
func TestPredefinedPool(t *testing.T) {
a, err := NewAllocator(nil, nil)
if err != nil {
t.Fatal(err)
}
if _, err := a.getPredefinedPool("blue", false); err == nil {
t.Fatalf("Expected failure for non default addr space")
}
i, available, err := getFirstAvailablePool(a, localAddressSpace, 2)
if err != nil {
t.Skip(err)
}
pid, _, _, err := a.RequestPool(localAddressSpace, available.String(), "", nil, false)
if err != nil {
t.Fatal(err)
}
nw, err := a.getPredefinedPool(localAddressSpace, false)
if err != nil {
t.Fatal(err)
}
if nw != a.predefined[localAddressSpace][i+1] {
t.Fatalf("Unexpected default network returned: %s", nw)
}
if err := a.ReleasePool(pid); err != nil {
t.Fatal(err)
}
nw, err = a.getPredefinedPool(localAddressSpace, false)
if err != nil {
t.Fatal(err)
}
if nw != a.predefined[localAddressSpace][i] {
t.Fatalf("Unexpected default network returned: %s", nw)
}
i, available, err = getFirstAvailablePool(a, globalAddressSpace, 2)
if err != nil {
t.Skip(err)
}
nw, err = a.getPredefinedPool(globalAddressSpace, false)
if err != nil {
t.Fatal(err)
}
if nw != available {
t.Fatalf("Unexpected default network returned: %s", nw)
}
}
func getFirstAvailablePool(a *Allocator, as string, atLeast int) (int, *net.IPNet, error) {
i := 0
for i < len(a.predefined[as])-1 {
if err := netutils.CheckRouteOverlaps(a.predefined[as][i]); err == nil {
break
}
i++
}
if i > len(a.predefined[as])-1-atLeast {
return 0, nil, fmt.Errorf("Not enough non-overlapping networks to run the test")
}
return i, a.predefined[as][i], nil
}
func TestAdjustAndCheckSubnet(t *testing.T) {
_, sub6, _ := net.ParseCIDR("1003:1:2:300::/63")
_, err := adjustAndCheckSubnetSize(sub6)
@ -172,13 +444,13 @@ func TestAdjustAndCheckSubnet(t *testing.T) {
}
func TestRemoveSubnet(t *testing.T) {
a, err := NewAllocator(nil)
a, err := NewAllocator(nil, nil)
if err != nil {
t.Fatal(err)
}
input := []struct {
addrSpace AddressSpace
addrSpace string
subnet string
}{
{"default", "192.168.0.0/16"},
@ -191,119 +463,116 @@ func TestRemoveSubnet(t *testing.T) {
{"splane", "2002:1:2:3:4:5:ffff::/112"},
}
for _, i := range input {
_, sub, err := net.ParseCIDR(i.subnet)
if err != nil {
t.Fatalf("Wrong input, Can't proceed: %s", err.Error())
}
err = a.AddSubnet(i.addrSpace, &SubnetInfo{Subnet: sub})
if err != nil {
poolIDs := make([]string, len(input))
for ind, i := range input {
if poolIDs[ind], _, _, err = a.RequestPool(i.addrSpace, i.subnet, "", nil, false); err != nil {
t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error())
}
}
_, sub, _ := net.ParseCIDR("172.17.0.0/16")
a.RemoveSubnet("default", sub)
if len(a.subnets) != 7 {
t.Fatalf("Failed to remove subnet info")
}
list := a.getSubnetList("default", v4)
if len(list) != 257 {
t.Fatalf("Failed to effectively remove subnet address space")
}
_, sub, _ = net.ParseCIDR("2002:1:2:3:4:5:ffff::/112")
a.RemoveSubnet("default", sub)
if len(a.subnets) != 6 {
t.Fatalf("Failed to remove subnet info")
}
list = a.getSubnetList("default", v6)
if len(list) != 0 {
t.Fatalf("Failed to effectively remove subnet address space")
}
_, sub, _ = net.ParseCIDR("2002:1:2:3:4:5:6::/112")
a.RemoveSubnet("splane", sub)
if len(a.subnets) != 5 {
t.Fatalf("Failed to remove subnet info")
}
list = a.getSubnetList("splane", v6)
if len(list) != 1 {
t.Fatalf("Failed to effectively remove subnet address space")
}
}
func TestGetInternalSubnets(t *testing.T) {
// This function tests the splitting of a parent subnet in small host subnets.
// The splitting is controlled by the max host size, which is the first parameter
// passed to the function. It basically says if the parent subnet host size is
// greater than the max host size, split the parent subnet into N internal small
// subnets with host size = max host size to cover the same address space.
input := []struct {
internalHostSize int
parentSubnet string
firstIntSubnet string
lastIntSubnet string
}{
// Test 8 bits prefix network
{24, "10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"},
{16, "10.0.0.0/8", "10.0.0.0/16", "10.255.0.0/16"},
{8, "10.0.0.0/8", "10.0.0.0/24", "10.255.255.0/24"},
// Test 16 bits prefix network
{16, "192.168.0.0/16", "192.168.0.0/16", "192.168.0.0/16"},
{8, "192.168.0.0/16", "192.168.0.0/24", "192.168.255.0/24"},
// Test 24 bits prefix network
{16, "192.168.57.0/24", "192.168.57.0/24", "192.168.57.0/24"},
{8, "192.168.57.0/24", "192.168.57.0/24", "192.168.57.0/24"},
// Test non byte multiple host size
{24, "10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"},
{20, "10.0.0.0/12", "10.0.0.0/12", "10.0.0.0/12"},
{20, "10.128.0.0/12", "10.128.0.0/12", "10.128.0.0/12"},
{12, "10.16.0.0/16", "10.16.0.0/20", "10.16.240.0/20"},
{13, "10.0.0.0/8", "10.0.0.0/19", "10.255.224.0/19"},
{15, "10.0.0.0/8", "10.0.0.0/17", "10.255.128.0/17"},
// Test v6 network
{16, "2002:1:2:3:4:5:6000::/110", "2002:1:2:3:4:5:6000:0/112", "2002:1:2:3:4:5:6003:0/112"},
{16, "2002:1:2:3:4:5:ff00::/104", "2002:1:2:3:4:5:ff00:0/112", "2002:1:2:3:4:5:ffff:0/112"},
{12, "2002:1:2:3:4:5:ffff::/112", "2002:1:2:3:4:5:ffff:0/116", "2002:1:2:3:4:5:ffff:f000/116"},
{11, "2002:1:2:3:4:5:ffff::/112", "2002:1:2:3:4:5:ffff:0/117", "2002:1:2:3:4:5:ffff:f800/117"},
}
for _, d := range input {
assertInternalSubnet(t, d.internalHostSize, d.parentSubnet, d.firstIntSubnet, d.lastIntSubnet)
for ind, id := range poolIDs {
if err := a.ReleasePool(id); err != nil {
t.Fatalf("Failed to release poolID %s (%d)", id, ind)
}
}
}
func TestGetSameAddress(t *testing.T) {
a, err := NewAllocator(nil)
a, err := NewAllocator(nil, nil)
if err != nil {
t.Fatal(err)
}
addSpace := AddressSpace("giallo")
_, subnet, _ := net.ParseCIDR("192.168.100.0/24")
if err := a.AddSubnet(addSpace, &SubnetInfo{Subnet: subnet}); err != nil {
pid, _, _, err := a.RequestPool("giallo", "192.168.100.0/24", "", nil, false)
if err != nil {
t.Fatal(err)
}
ip := net.ParseIP("192.168.100.250")
req := &AddressRequest{Subnet: *subnet, Address: ip}
_, err = a.Request(addSpace, req)
_, _, err = a.RequestAddress(pid, ip, nil)
if err != nil {
t.Fatal(err)
}
_, err = a.Request(addSpace, req)
_, _, err = a.RequestAddress(pid, ip, nil)
if err == nil {
t.Fatal(err)
}
}
func TestRequestReleaseAddressFromSubPool(t *testing.T) {
a, err := NewAllocator(nil, nil)
if err != nil {
t.Fatal(err)
}
poolID, _, _, err := a.RequestPool("rosso", "172.28.0.0/16", "172.28.30.0/24", nil, false)
if err != nil {
t.Fatal(err)
}
var ip *net.IPNet
expected := &net.IPNet{IP: net.IP{172, 28, 30, 255}, Mask: net.IPMask{255, 255, 0, 0}}
for err == nil {
var c *net.IPNet
if c, _, err = a.RequestAddress(poolID, nil, nil); err == nil {
ip = c
}
}
if err != ipamapi.ErrNoAvailableIPs {
t.Fatal(err)
}
if !types.CompareIPNet(expected, ip) {
t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
}
rp := &net.IPNet{IP: net.IP{172, 28, 30, 97}, Mask: net.IPMask{255, 255, 0, 0}}
if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(rp, ip) {
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
}
_, _, _, err = a.RequestPool("rosso", "10.0.0.0/8", "10.0.0.0/16", nil, false)
if err != nil {
t.Fatal(err)
}
poolID, _, _, err = a.RequestPool("rosso", "10.0.0.0/16", "10.0.0.0/24", nil, false)
if err != nil {
t.Fatal(err)
}
expected = &net.IPNet{IP: net.IP{10, 0, 0, 255}, Mask: net.IPMask{255, 255, 0, 0}}
for err == nil {
var c *net.IPNet
if c, _, err = a.RequestAddress(poolID, nil, nil); err == nil {
ip = c
}
}
if err != ipamapi.ErrNoAvailableIPs {
t.Fatal(err)
}
if !types.CompareIPNet(expected, ip) {
t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip)
}
rp = &net.IPNet{IP: net.IP{10, 0, 0, 79}, Mask: net.IPMask{255, 255, 0, 0}}
if err = a.ReleaseAddress(poolID, rp.IP); err != nil {
t.Fatal(err)
}
if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil {
t.Fatal(err)
}
if !types.CompareIPNet(rp, ip) {
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
}
}
func TestGetAddress(t *testing.T) {
input := []string{
/*"10.0.0.0/8", "10.0.0.0/9", */ "10.0.0.0/10", "10.0.0.0/11", "10.0.0.0/12", "10.0.0.0/13", "10.0.0.0/14",
/*"10.0.0.0/8", "10.0.0.0/9", "10.0.0.0/10",*/ "10.0.0.0/11", "10.0.0.0/12", "10.0.0.0/13", "10.0.0.0/14",
"10.0.0.0/15", "10.0.0.0/16", "10.0.0.0/17", "10.0.0.0/18", "10.0.0.0/19", "10.0.0.0/20", "10.0.0.0/21",
"10.0.0.0/22", "10.0.0.0/23", "10.0.0.0/24", "10.0.0.0/25", "10.0.0.0/26", "10.0.0.0/27", "10.0.0.0/28",
"10.0.0.0/29", "10.0.0.0/30", "10.0.0.0/31"}
@ -313,90 +582,65 @@ func TestGetAddress(t *testing.T) {
}
}
func TestGetSubnetList(t *testing.T) {
a, err := NewAllocator(nil)
if err != nil {
t.Fatal(err)
}
input := []struct {
addrSpace AddressSpace
subnet string
}{
{"default", "192.168.0.0/16"},
{"default", "172.17.0.0/16"},
{"default", "10.0.0.0/8"},
{"default", "2002:1:2:3:4:5:6::/112"},
{"default", "2002:1:2:3:4:5:ffff::/112"},
{"splane", "172.17.0.0/16"},
{"splane", "10.0.0.0/8"},
{"splane", "2002:1:2:3:4:5:ff00::/104"},
}
for _, i := range input {
_, sub, err := net.ParseCIDR(i.subnet)
if err != nil {
t.Fatalf("Wrong input, Can't proceed: %s", err.Error())
}
err = a.AddSubnet(i.addrSpace, &SubnetInfo{Subnet: sub})
if err != nil {
t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error())
}
}
list := a.getSubnetList("default", v4)
if len(list) != 258 {
t.Fatalf("Incorrect number of internal subnets for ipv4 version. Expected 258. Got %d.", len(list))
}
list = a.getSubnetList("splane", v4)
if len(list) != 257 {
t.Fatalf("Incorrect number of internal subnets for ipv4 version. Expected 257. Got %d.", len(list))
}
list = a.getSubnetList("default", v6)
if len(list) != 2 {
t.Fatalf("Incorrect number of internal subnets for ipv6 version. Expected 2. Got %d.", len(list))
}
list = a.getSubnetList("splane", v6)
if len(list) != 256 {
t.Fatalf("Incorrect number of internal subnets for ipv6 version. Expected 256. Got %d.", len(list))
}
}
func TestRequestSyntaxCheck(t *testing.T) {
var (
subnet = "192.168.0.0/16"
addSpace = AddressSpace("green")
pool = "192.168.0.0/16"
subPool = "192.168.0.0/24"
addrSpace = "green"
)
a, err := NewAllocator(nil)
if err != nil {
t.Fatal(err)
}
a, _ := NewAllocator(nil, nil)
// Add subnet and create base request
_, sub, _ := net.ParseCIDR(subnet)
a.AddSubnet(addSpace, &SubnetInfo{Subnet: sub})
req := &AddressRequest{Subnet: *sub}
// Empty address space request
_, err = a.Request("", req)
_, _, _, err := a.RequestPool("", pool, "", nil, false)
if err == nil {
t.Fatalf("Failed to detect wrong request: empty address space")
}
// Preferred address from different subnet in request
req.Address = net.ParseIP("172.17.0.23")
_, err = a.Request(addSpace, req)
_, _, _, err = a.RequestPool("", pool, subPool, nil, false)
if err == nil {
t.Fatalf("Failed to detect wrong request: empty address space")
}
_, _, _, err = a.RequestPool(addrSpace, "", subPool, nil, false)
if err == nil {
t.Fatalf("Failed to detect wrong request: subPool specified and no pool")
}
pid, _, _, err := a.RequestPool(addrSpace, pool, subPool, nil, false)
if err != nil {
t.Fatalf("Unexpected failure: %v", err)
}
_, _, err = a.RequestAddress("", nil, nil)
if err == nil {
t.Fatalf("Failed to detect wrong request: no pool id specified")
}
ip := net.ParseIP("172.17.0.23")
_, _, err = a.RequestAddress(pid, ip, nil)
if err == nil {
t.Fatalf("Failed to detect wrong request: preferred IP from different subnet")
}
// Preferred address specified and nil subnet
req = &AddressRequest{Address: net.ParseIP("172.17.0.23")}
_, err = a.Request(addSpace, req)
ip = net.ParseIP("192.168.0.50")
_, _, err = a.RequestAddress(pid, ip, nil)
if err != nil {
t.Fatalf("Unexpected failure: %v", err)
}
err = a.ReleaseAddress("", ip)
if err == nil {
t.Fatalf("Failed to detect wrong request: subnet not specified but preferred address specified")
t.Fatalf("Failed to detect wrong request: no pool id specified")
}
err = a.ReleaseAddress(pid, nil)
if err == nil {
t.Fatalf("Failed to detect wrong request: no pool id specified")
}
err = a.ReleaseAddress(pid, ip)
if err != nil {
t.Fatalf("Unexpected failure: %v", err)
}
}
@ -433,18 +677,15 @@ func TestRequest(t *testing.T) {
func TestRelease(t *testing.T) {
var (
err error
req *AddressRequest
subnet = "192.168.0.0/16"
)
_, sub, _ := net.ParseCIDR(subnet)
a := getAllocator(t, sub)
req = &AddressRequest{Subnet: *sub}
bm := a.addresses[subnetKey{"default", subnet, subnet}]
a, pid := getAllocator(t, subnet)
bm := a.addresses[SubnetKey{"default", subnet, ""}]
// Allocate all addresses
for err != ErrNoAvailableIPs {
_, err = a.Request("default", req)
for err != ipamapi.ErrNoAvailableIPs {
_, _, err = a.RequestAddress(pid, nil, nil)
}
toRelease := []struct {
@ -475,45 +716,24 @@ func TestRelease(t *testing.T) {
}
// One by one, relase the address and request again. We should get the same IP
req = &AddressRequest{Subnet: *sub}
for i, inp := range toRelease {
address := net.ParseIP(inp.address)
a.Release("default", address)
ip0 := net.ParseIP(inp.address)
a.ReleaseAddress(pid, ip0)
if bm.Unselected() != 1 {
t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected())
}
rsp, err := a.Request("default", req)
nw, _, err := a.RequestAddress(pid, nil, nil)
if err != nil {
t.Fatalf("Failed to obtain the address: %s", err.Error())
}
if !address.Equal(rsp.Address) {
t.Fatalf("Failed to obtain the same address. Expected: %s, Got: %s", address, rsp.Address)
ip := nw.IP
if !ip0.Equal(ip) {
t.Fatalf("Failed to obtain the same address. Expected: %s, Got: %s", ip0, ip)
}
}
}
func assertInternalSubnet(t *testing.T, hostSize int, bigSubnet, firstSmall, lastSmall string) {
_, subnet, _ := net.ParseCIDR(bigSubnet)
list, _ := getInternalSubnets(subnet, hostSize)
count := 1
ones, bits := subnet.Mask.Size()
diff := bits - ones - int(hostSize)
if diff > 0 {
count <<= uint(diff)
}
if len(list) != count {
t.Fatalf("Wrong small subnets number. Expected: %d, Got: %d", count, len(list))
}
if firstSmall != list[0].String() {
t.Fatalf("Wrong first small subent. Expected: %v, Got: %v", firstSmall, list[0])
}
if lastSmall != list[count-1].String() {
t.Fatalf("Wrong last small subent. Expected: %v, Got: %v", lastSmall, list[count-1])
}
}
func assertGetAddress(t *testing.T, subnet string) {
var (
err error
@ -526,15 +746,15 @@ func assertGetAddress(t *testing.T, subnet string) {
zeroes := bits - ones
numAddresses := 1 << uint(zeroes)
bm, err := bitseq.NewHandle("ipam_test", nil, "default/192.168.0.0/24", uint32(numAddresses))
bm, err := bitseq.NewHandle("ipam_test", nil, "default/"+subnet, uint32(numAddresses))
if err != nil {
t.Fatal(err)
}
start := time.Now()
run := 0
for err != ErrNoAvailableIPs {
_, err = a.getAddress(sub, bm, nil, v4)
for err != ipamapi.ErrNoAvailableIPs {
_, err = a.getAddress(sub, bm, nil, nil)
run++
}
if printTime {
@ -554,59 +774,52 @@ func assertGetAddress(t *testing.T, subnet string) {
func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP string) {
var (
err error
req *AddressRequest
rsp *AddressResponse
nw *net.IPNet
printTime = false
)
_, sub, _ := net.ParseCIDR(subnet)
lastIP := net.ParseIP(lastExpectedIP)
a := getAllocator(t, sub)
req = &AddressRequest{Subnet: *sub}
a, pid := getAllocator(t, subnet)
i := 0
start := time.Now()
for ; i < numReq; i++ {
rsp, err = a.Request("default", req)
nw, _, err = a.RequestAddress(pid, nil, nil)
}
if printTime {
fmt.Printf("\nTaken %v, to allocate %d addresses on %s\n", time.Since(start), numReq, subnet)
}
if !lastIP.Equal(rsp.Address) {
t.Fatalf("Wrong last IP. Expected %s. Got: %s (err: %v, ind: %d)", lastExpectedIP, rsp.Address.String(), err, i)
if !lastIP.Equal(nw.IP) {
t.Fatalf("Wrong last IP. Expected %s. Got: %s (err: %v, ind: %d)", lastExpectedIP, nw.IP.String(), err, i)
}
}
func benchmarkRequest(subnet *net.IPNet) {
func benchmarkRequest(subnet string) {
var err error
a, _ := NewAllocator(nil)
a.internalHostSize = 20
a.AddSubnet("default", &SubnetInfo{Subnet: subnet})
req := &AddressRequest{Subnet: *subnet}
for err != ErrNoAvailableIPs {
_, err = a.Request("default", req)
a, _ := NewAllocator(nil, nil)
pid, _, _, _ := a.RequestPool("default", subnet, "", nil, false)
for err != ipamapi.ErrNoAvailableIPs {
_, _, err = a.RequestAddress(pid, nil, nil)
}
}
func benchMarkRequest(subnet *net.IPNet, b *testing.B) {
func benchMarkRequest(subnet string, b *testing.B) {
for n := 0; n < b.N; n++ {
benchmarkRequest(subnet)
}
}
func BenchmarkRequest_24(b *testing.B) {
benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 255, 255, 0}})
benchmarkRequest("10.0.0.0/24")
}
func BenchmarkRequest_16(b *testing.B) {
benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 255, 0, 0}})
benchmarkRequest("10.0.0.0/16")
}
func BenchmarkRequest_8(b *testing.B) {
benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 0xfc, 0, 0}})
benchmarkRequest("10.0.0.0/8")
}

View file

@ -1,100 +0,0 @@
// Package ipam that specifies the contract the IPAM plugin need to satisfy,
// decoupling IPAM interface and implementation.
package ipam
import (
"errors"
"net"
)
/**************
* IPAM Errors
**************/
// ErrIpamNotAvailable is returned when the plugin prviding the IPAM service is not available
var (
ErrInvalidIpamService = errors.New("Invalid IPAM Service")
ErrInvalidIpamConfigService = errors.New("Invalid IPAM Config Service")
ErrIpamNotAvailable = errors.New("IPAM Service not available")
ErrIpamInternalError = errors.New("IPAM Internal Error")
ErrInvalidAddressSpace = errors.New("Invalid Address Space")
ErrInvalidSubnet = errors.New("Invalid Subnet")
ErrInvalidRequest = errors.New("Invalid Request")
ErrSubnetNotFound = errors.New("Subnet not found")
ErrOverlapSubnet = errors.New("Subnet overlaps with existing subnet on this address space")
ErrNoAvailableSubnet = errors.New("No available subnet")
ErrNoAvailableIPs = errors.New("No available addresses on subnet")
ErrIPAlreadyAllocated = errors.New("Address already in use")
ErrIPOutOfRange = errors.New("Requested address is out of range")
ErrSubnetAlreadyRegistered = errors.New("Subnet already registered on this address space")
ErrBadSubnet = errors.New("Address space does not contain specified subnet")
)
// AddressSpace identifies a unique pool of network addresses
type AddressSpace string
/*******************************
* IPAM Configuration Interface
*******************************/
// Config represents the interface the IPAM service plugins must implement
// in order to allow injection/modification of IPAM database.
// Common key is a addressspace
type Config interface {
// AddSubnet adds a subnet to the specified address space
AddSubnet(AddressSpace, *SubnetInfo) error
// RemoveSubnet removes a subnet from the specified address space
RemoveSubnet(AddressSpace, *net.IPNet) error
// AddVendorInfo adds Vendor specific data
AddVendorInfo([]byte) error
}
// SubnetInfo contains the information subnet hosts need in order to communicate
type SubnetInfo struct {
Subnet *net.IPNet
Gateway net.IP
OpaqueData []byte // Vendor specific
}
/*************************
* IPAM Service Interface
*************************/
// IPAM defines the interface that needs to be implemented by IPAM service plugin
// Common key is a unique address space identifier
type IPAM interface {
// Request address from the specified address space
Request(AddressSpace, *AddressRequest) (*AddressResponse, error)
// Separate API for IPv6
RequestV6(AddressSpace, *AddressRequest) (*AddressResponse, error)
// Release the address from the specified address space
Release(AddressSpace, net.IP)
}
// AddressRequest encloses the information a client
// needs to pass to IPAM when requesting an address
type AddressRequest struct {
Subnet net.IPNet // Preferred subnet pool (Optional)
Address net.IP // Preferred address (Optional)
Endpoint string // For static IP mapping (Optional)
OpaqueData []byte // Vendor specific request data
}
// Validate runs syntactic validation on this AddressRequest object
func (req *AddressRequest) Validate() error {
var byteArray []byte = req.Address
// Check preferred address
if byteArray != nil && (&req.Subnet == nil || !req.Subnet.Contains(req.Address)) {
return ErrInvalidRequest
}
return nil
}
// AddressResponse represents the IPAM service's
// response to an address request
type AddressResponse struct {
Address net.IP
Subnet SubnetInfo
}

View file

@ -2,7 +2,6 @@ package ipam
import (
"encoding/json"
"net"
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/datastore"
@ -13,14 +12,14 @@ import (
func (a *Allocator) Key() []string {
a.Lock()
defer a.Unlock()
return []string{a.App, a.ID}
return []string{dsConfigKey}
}
// KeyPrefix returns the immediate parent key that can be used for tree walk
func (a *Allocator) KeyPrefix() []string {
a.Lock()
defer a.Unlock()
return []string{a.App}
return []string{dsConfigKey}
}
// Value marshals the data to be stored in the KV store
@ -31,9 +30,14 @@ func (a *Allocator) Value() []byte {
if a.subnets == nil {
return []byte{}
}
m := map[string]interface{}{}
for k, v := range a.subnets {
m[k.String()] = v
}
b, err := subnetsToByteArray(a.subnets)
b, err := json.Marshal(m)
if err != nil {
log.Warnf("Failed to marshal ipam configured subnets")
return nil
}
return b
@ -41,52 +45,19 @@ func (a *Allocator) Value() []byte {
// SetValue unmarshalls the data from the KV store.
func (a *Allocator) SetValue(value []byte) error {
a.subnets = byteArrayToSubnets(value)
return nil
}
func subnetsToByteArray(m map[subnetKey]*SubnetInfo) ([]byte, error) {
if m == nil {
return nil, nil
}
mm := make(map[string]string, len(m))
for k, v := range m {
mm[k.String()] = v.Subnet.String()
}
return json.Marshal(mm)
}
func byteArrayToSubnets(ba []byte) map[subnetKey]*SubnetInfo {
m := map[subnetKey]*SubnetInfo{}
if ba == nil || len(ba) == 0 {
return m
}
var mm map[string]string
err := json.Unmarshal(ba, &mm)
var m map[string]*PoolData
err := json.Unmarshal(value, &m)
if err != nil {
log.Warnf("Failed to decode subnets byte array: %v", err)
return m
return err
}
for ks, vs := range mm {
sk := subnetKey{}
if err := sk.FromString(ks); err != nil {
log.Warnf("Failed to decode subnets map entry: (%s, %s)", ks, vs)
continue
for ks, d := range m {
k := SubnetKey{}
if err := k.FromString(ks); err != nil {
return err
}
si := &SubnetInfo{}
_, nw, err := net.ParseCIDR(vs)
if err != nil {
log.Warnf("Failed to decode subnets map entry value: (%s, %s)", ks, vs)
continue
}
si.Subnet = nw
m[sk] = si
a.subnets[k] = d
}
return m
return nil
}
// Index returns the latest DB Index as seen by this object
@ -130,7 +101,6 @@ func (a *Allocator) watchForChanges() error {
select {
case kvPair := <-kvpChan:
if kvPair != nil {
log.Debugf("Got notification for key %v: %v", kvPair.Key, kvPair.Value)
a.subnetConfigFromStore(kvPair)
}
}

View file

@ -0,0 +1,72 @@
// Package ipamapi specifies the contract the IPAM service (built-in or remote) needs to satisfy.
package ipamapi
import (
"errors"
"net"
)
/********************
* IPAM plugin types
********************/
const (
// DefaultIPAM is the name of the built-in default ipam driver
DefaultIPAM = "default"
// PluginEndpointType represents the Endpoint Type used by Plugin system
PluginEndpointType = "IPAM"
)
// Callback provides a Callback interface for registering an IPAM instance into LibNetwork
type Callback interface {
// RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a ipam instance
RegisterIpamDriver(name string, driver Ipam) error
}
/**************
* IPAM Errors
**************/
// Weel-known errors returned by IPAM
var (
ErrInvalidIpamService = errors.New("Invalid IPAM Service")
ErrInvalidIpamConfigService = errors.New("Invalid IPAM Config Service")
ErrIpamNotAvailable = errors.New("IPAM Service not available")
ErrIpamInternalError = errors.New("IPAM Internal Error")
ErrInvalidAddressSpace = errors.New("Invalid Address Space")
ErrInvalidPool = errors.New("Invalid Address Pool")
ErrInvalidSubPool = errors.New("Invalid Address SubPool")
ErrInvalidRequest = errors.New("Invalid Request")
ErrPoolNotFound = errors.New("Address Pool not found")
ErrOverlapPool = errors.New("Address pool overlaps with existing pool on this address space")
ErrNoAvailablePool = errors.New("No available pool")
ErrNoAvailableIPs = errors.New("No available addresses on this pool")
ErrIPAlreadyAllocated = errors.New("Address already in use")
ErrIPOutOfRange = errors.New("Requested address is out of range")
ErrPoolOverlap = errors.New("Pool overlaps with other one on this address space")
ErrBadPool = errors.New("Address space does not contain specified address pool")
)
/*******************************
* IPAM Service Interface
*******************************/
// Ipam represents the interface the IPAM service plugins must implement
// in order to allow injection/modification of IPAM database.
type Ipam interface {
// GetDefaultAddressSpaces returns the default local and global address spaces for this ipam
GetDefaultAddressSpaces() (string, string, error)
// RequestPool returns an address pool along with its unique id. Address space is a mandatory field
// which denotes a set of non-overlapping pools. pool describes the pool of addrresses in CIDR notation.
// subpool indicates a smaller range of addresses from the pool, for now it is specified in CIDR notation.
// Both pool and subpool are non mandatory fields. When they are not specified, Ipam driver may choose to
// return a self chosen pool for this request. In such case the v6 flag needs to be set appropriately so
// that the driver would return the expected ip version pool.
RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error)
// ReleasePool releases the address pool identified by the passed id
ReleasePool(poolID string) error
// Request address from the specified pool ID. Input options or preferred IP can be passed.
RequestAddress(string, net.IP, map[string]string) (*net.IPNet, map[string]string, error)
// Release the address from the specified pool ID
ReleaseAddress(string, net.IP) error
}

View file

@ -0,0 +1,35 @@
package builtin
import (
"fmt"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/ipam"
"github.com/docker/libnetwork/ipamapi"
)
// Init registers the built-in ipam service with libnetwork
func Init(ic ipamapi.Callback, l, g interface{}) error {
var (
ok bool
localDs, globalDs datastore.DataStore
)
if l != nil {
if localDs, ok = l.(datastore.DataStore); !ok {
return fmt.Errorf("incorrect local datastore passed to built-in ipam init")
}
}
if g != nil {
if globalDs, ok = g.(datastore.DataStore); !ok {
return fmt.Errorf("incorrect global datastore passed to built-in ipam init")
}
}
a, err := ipam.NewAllocator(localDs, globalDs)
if err != nil {
return err
}
return ic.RegisterIpamDriver(ipamapi.DefaultIPAM, a)
}

View file

@ -0,0 +1,81 @@
// Package api defines the data structure to be used in the request/response
// messages between libnetwork and the remote ipam plugin
package api
import (
"net"
)
// Response is the basic response structure used in all responses
type Response struct {
Error string
}
// IsSuccess returns wheter the plugin response is successful
func (r *Response) IsSuccess() bool {
return r.Error == ""
}
// GetError returns the error from the response, if any.
func (r *Response) GetError() string {
return r.Error
}
// GetAddressSpacesResponse is the response to the ``get default address spaces`` request message
type GetAddressSpacesResponse struct {
Response
LocalDefaultAddressSpace string
GlobalDefaultAddressSpace string
}
// RequestPoolRequest represents the expected data in a ``request address pool`` request message
type RequestPoolRequest struct {
AddressSpace string
Pool string
SubPool string
Options map[string]string
V6 bool
}
// RequestPoolResponse represents the response message to a ``request address pool`` request
type RequestPoolResponse struct {
Response
PoolID string
Pool *net.IPNet
Data map[string]string
}
// ReleasePoolRequest represents the expected data in a ``release address pool`` request message
type ReleasePoolRequest struct {
PoolID string
}
// ReleasePoolResponse represents the response message to a ``release address pool`` request
type ReleasePoolResponse struct {
Response
}
// RequestAddressRequest represents the expected data in a ``request address`` request message
type RequestAddressRequest struct {
PoolID string
Address net.IP
Options map[string]string
}
// RequestAddressResponse represents the expected data in the response message to a ``request address`` request
type RequestAddressResponse struct {
Response
Address *net.IPNet
Data map[string]string
}
// ReleaseAddressRequest represents the expected data in a ``release address`` request message
type ReleaseAddressRequest struct {
PoolID string
Address net.IP
}
// ReleaseAddressResponse represents the response message to a ``release address`` request
type ReleaseAddressResponse struct {
Response
}

View file

@ -0,0 +1,92 @@
package remote
import (
"fmt"
"net"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/ipams/remote/api"
)
type allocator struct {
endpoint *plugins.Client
name string
}
// PluginResponse is the interface for the plugin request responses
type PluginResponse interface {
IsSuccess() bool
GetError() string
}
func newAllocator(name string, client *plugins.Client) ipamapi.Ipam {
a := &allocator{name: name, endpoint: client}
return a
}
// Init registers a remote ipam when its plugin is activated
func Init(cb ipamapi.Callback, l, g interface{}) error {
plugins.Handle(ipamapi.PluginEndpointType, func(name string, client *plugins.Client) {
if err := cb.RegisterIpamDriver(name, newAllocator(name, client)); err != nil {
log.Errorf("error registering remote ipam %s due to %v", name, err)
}
})
return nil
}
func (a *allocator) call(methodName string, arg interface{}, retVal PluginResponse) error {
method := ipamapi.PluginEndpointType + "." + methodName
err := a.endpoint.Call(method, arg, retVal)
if err != nil {
return err
}
if !retVal.IsSuccess() {
return fmt.Errorf("remote: %s", retVal.GetError())
}
return nil
}
// GetDefaultAddressSpaces returns the local and global default address spaces
func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
res := &api.GetAddressSpacesResponse{}
if err := a.call("GetDefaultAddressSpaces", nil, res); err != nil {
return "", "", err
}
return res.LocalDefaultAddressSpace, res.GlobalDefaultAddressSpace, nil
}
// RequestPool requests an address pool in the specified address space
func (a *allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
req := &api.RequestPoolRequest{AddressSpace: addressSpace, Pool: pool, SubPool: subPool, Options: options, V6: v6}
res := &api.RequestPoolResponse{}
if err := a.call("RequestPool", req, res); err != nil {
return "", nil, nil, err
}
return res.PoolID, res.Pool, res.Data, nil
}
// ReleasePool removes an address pool from the specified address space
func (a *allocator) ReleasePool(poolID string) error {
req := &api.ReleasePoolRequest{PoolID: poolID}
res := &api.ReleasePoolResponse{}
return a.call("ReleasePool", req, res)
}
// RequestAddress requests an address from the address pool
func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
req := &api.RequestAddressRequest{PoolID: poolID, Address: address, Options: options}
res := &api.RequestAddressResponse{}
if err := a.call("RequestAddress", req, res); err != nil {
return nil, nil, err
}
return res.Address, res.Data, nil
}
// ReleaseAddress releases the address from the specified address pool
func (a *allocator) ReleaseAddress(poolID string, address net.IP) error {
req := &api.ReleaseAddressRequest{PoolID: poolID, Address: address}
res := &api.ReleaseAddressResponse{}
return a.call("ReleaseAddress", req, res)
}

View file

@ -1,10 +1,15 @@
package libnetwork
import (
"encoding/json"
"fmt"
"net"
"testing"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/types"
)
func TestDriverRegistration(t *testing.T) {
@ -31,3 +36,264 @@ func SetTestDataStore(c NetworkController, custom datastore.DataStore) {
con := c.(*controller)
con.globalStore = custom
}
func TestNetworkMarshalling(t *testing.T) {
n := &network{
name: "Miao",
id: "abccba",
ipamType: "default",
addrSpace: "viola",
networkType: "bridge",
endpointCnt: 27,
enableIPv6: true,
persist: true,
ipamV4Config: []*IpamConf{
&IpamConf{
PreferredPool: "10.2.0.0/16",
SubPool: "10.2.0.0/24",
Options: map[string]string{
netlabel.MacAddress: "a:b:c:d:e:f",
},
Gateway: "",
AuxAddresses: nil,
},
&IpamConf{
PreferredPool: "10.2.0.0/16",
SubPool: "10.2.1.0/24",
Options: nil,
Gateway: "10.2.1.254",
},
},
ipamV6Config: []*IpamConf{
&IpamConf{
PreferredPool: "abcd::/64",
SubPool: "abcd:abcd:abcd:abcd:abcd::/80",
Gateway: "abcd::29/64",
AuxAddresses: nil,
},
},
ipamV4Info: []*IpamInfo{
&IpamInfo{
PoolID: "ipoolverde123",
Meta: map[string]string{
netlabel.Gateway: "10.2.1.255/16",
},
IPAMData: driverapi.IPAMData{
AddressSpace: "viola",
Pool: &net.IPNet{
IP: net.IP{10, 2, 0, 0},
Mask: net.IPMask{255, 255, 255, 0},
},
Gateway: nil,
AuxAddresses: nil,
},
},
&IpamInfo{
PoolID: "ipoolblue345",
Meta: map[string]string{
netlabel.Gateway: "10.2.1.255/16",
},
IPAMData: driverapi.IPAMData{
AddressSpace: "viola",
Pool: &net.IPNet{
IP: net.IP{10, 2, 1, 0},
Mask: net.IPMask{255, 255, 255, 0},
},
Gateway: &net.IPNet{IP: net.IP{10, 2, 1, 254}, Mask: net.IPMask{255, 255, 255, 0}},
AuxAddresses: map[string]*net.IPNet{
"ip3": &net.IPNet{IP: net.IP{10, 2, 1, 3}, Mask: net.IPMask{255, 255, 255, 0}},
"ip5": &net.IPNet{IP: net.IP{10, 2, 1, 55}, Mask: net.IPMask{255, 255, 255, 0}},
},
},
},
&IpamInfo{
PoolID: "weirdinfo",
IPAMData: driverapi.IPAMData{
Gateway: &net.IPNet{
IP: net.IP{11, 2, 1, 255},
Mask: net.IPMask{255, 0, 0, 0},
},
},
},
},
ipamV6Info: []*IpamInfo{
&IpamInfo{
PoolID: "ipoolv6",
IPAMData: driverapi.IPAMData{
AddressSpace: "viola",
Pool: &net.IPNet{
IP: net.IP{0xab, 0xcd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0},
},
Gateway: &net.IPNet{
IP: net.IP{0xab, 0xcd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29},
Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0},
},
AuxAddresses: nil,
},
},
},
}
b, err := json.Marshal(n)
if err != nil {
t.Fatal(err)
}
nn := &network{}
err = json.Unmarshal(b, nn)
if err != nil {
t.Fatal(err)
}
if n.name != nn.name || n.id != nn.id || n.networkType != nn.networkType || n.ipamType != nn.ipamType ||
n.addrSpace != nn.addrSpace || n.endpointCnt != nn.endpointCnt || n.enableIPv6 != nn.enableIPv6 ||
n.persist != nn.persist || !compareIpamConfList(n.ipamV4Config, nn.ipamV4Config) ||
!compareIpamInfoList(n.ipamV4Info, nn.ipamV4Info) || !compareIpamConfList(n.ipamV6Config, nn.ipamV6Config) ||
!compareIpamInfoList(n.ipamV6Info, nn.ipamV6Info) {
t.Fatalf("JSON marsh/unmarsh failed."+
"\nOriginal:\n%#v\nDecoded:\n%#v"+
"\nOriginal ipamV4Conf: %#v\n\nDecoded ipamV4Conf: %#v"+
"\nOriginal ipamV4Info: %s\n\nDecoded ipamV4Info: %s"+
"\nOriginal ipamV6Conf: %#v\n\nDecoded ipamV6Conf: %#v"+
"\nOriginal ipamV6Info: %s\n\nDecoded ipamV6Info: %s",
n, nn, printIpamConf(n.ipamV4Config), printIpamConf(nn.ipamV4Config),
printIpamInfo(n.ipamV4Info), printIpamInfo(nn.ipamV4Info),
printIpamConf(n.ipamV6Config), printIpamConf(nn.ipamV6Config),
printIpamInfo(n.ipamV6Info), printIpamInfo(nn.ipamV6Info))
}
}
func printIpamConf(list []*IpamConf) string {
s := fmt.Sprintf("\n[]*IpamConfig{")
for _, i := range list {
s = fmt.Sprintf("%s %v,", s, i)
}
s = fmt.Sprintf("%s}", s)
return s
}
func printIpamInfo(list []*IpamInfo) string {
s := fmt.Sprintf("\n[]*IpamInfo{")
for _, i := range list {
s = fmt.Sprintf("%s\n{\n%s\n}", s, i)
}
s = fmt.Sprintf("%s\n}", s)
return s
}
func TestEndpointMarshalling(t *testing.T) {
ip, nw6, err := net.ParseCIDR("2001:3002:4003::122/64")
if err != nil {
t.Fatal(err)
}
nw6.IP = ip
e := &endpoint{
name: "Bau",
id: "efghijklmno",
sandboxID: "ambarabaciccicocco",
iface: &endpointInterface{
mac: []byte{11, 12, 13, 14, 15, 16},
addr: &net.IPNet{
IP: net.IP{10, 0, 1, 23},
Mask: net.IPMask{255, 255, 255, 0},
},
addrv6: nw6,
srcName: "veth12ab1314",
dstPrefix: "eth",
poolID: "poolpool",
},
}
b, err := json.Marshal(e)
if err != nil {
t.Fatal(err)
}
ee := &endpoint{}
err = json.Unmarshal(b, ee)
if err != nil {
t.Fatal(err)
}
if e.name != ee.name || e.id != ee.id || e.sandboxID != ee.sandboxID || !compareEndpointInterface(e.iface, ee.iface) {
t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%#v\nDecoded:\n%#v\nOriginal iface: %#v\nDecodediface:\n%#v", e, ee, e.iface, ee.iface)
}
}
func compareEndpointInterface(a, b *endpointInterface) bool {
if a == b {
return true
}
if a == nil || b == nil {
return false
}
return a.srcName == b.srcName && a.dstPrefix == b.dstPrefix && a.poolID == b.poolID &&
types.CompareIPNet(a.addr, b.addr) && types.CompareIPNet(a.addrv6, b.addrv6)
}
func compareIpamConfList(listA, listB []*IpamConf) bool {
var a, b *IpamConf
if len(listA) != len(listB) {
return false
}
for i := 0; i < len(listA); i++ {
a = listA[i]
b = listB[i]
if a.PreferredPool != b.PreferredPool ||
a.SubPool != b.SubPool || a.IsV6 != b.IsV6 ||
!compareStringMaps(a.Options, b.Options) ||
a.Gateway != b.Gateway || !compareStringMaps(a.AuxAddresses, b.AuxAddresses) {
return false
}
}
return true
}
func compareIpamInfoList(listA, listB []*IpamInfo) bool {
var a, b *IpamInfo
if len(listA) != len(listB) {
return false
}
for i := 0; i < len(listA); i++ {
a = listA[i]
b = listB[i]
if a.PoolID != b.PoolID || !compareStringMaps(a.Meta, b.Meta) ||
!types.CompareIPNet(a.Gateway, b.Gateway) ||
a.AddressSpace != b.AddressSpace ||
!types.CompareIPNet(a.Pool, b.Pool) ||
!compareAddresses(a.AuxAddresses, b.AuxAddresses) {
return false
}
}
return true
}
func compareStringMaps(a, b map[string]string) bool {
if len(a) != len(b) {
return false
}
if len(a) > 0 {
for k := range a {
if a[k] != b[k] {
return false
}
}
}
return true
}
func compareAddresses(a, b map[string]*net.IPNet) bool {
if len(a) != len(b) {
return false
}
if len(a) > 0 {
for k := range a {
if !types.CompareIPNet(a[k], b[k]) {
return false
}
}
}
return true
}

View file

@ -6,7 +6,6 @@ import (
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"os"
@ -268,23 +267,20 @@ func TestBridge(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
}
ip, subnet, err := net.ParseCIDR("192.168.100.1/24")
subnet, err := types.ParseCIDR("192.168.100.1/24")
if err != nil {
t.Fatal(err)
}
subnet.IP = ip
ip, cidr, err := net.ParseCIDR("192.168.100.2/28")
cidr, err := types.ParseCIDR("192.168.100.2/28")
if err != nil {
t.Fatal(err)
}
cidr.IP = ip
ip, cidrv6, err := net.ParseCIDR("fe90::1/96")
cidrv6, err := types.ParseCIDR("fe90::1/96")
if err != nil {
t.Fatal(err)
}
cidrv6.IP = ip
log.Debug("Adding a bridge")
@ -549,11 +545,10 @@ func TestUnknownEndpoint(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
}
ip, subnet, err := net.ParseCIDR("192.168.100.1/24")
subnet, err := types.ParseCIDR("192.168.100.1/24")
if err != nil {
t.Fatal(err)
}
subnet.IP = ip
netOption := options.Generic{
"BridgeName": "testnetwork",
@ -1026,10 +1021,9 @@ func TestEndpointJoin(t *testing.T) {
// Validate if ep.Info() only gives me IP address info and not names and gateway during CreateEndpoint()
info := ep1.Info()
if iface := info.Iface(); iface != nil {
if iface.Address().IP.To4() == nil {
t.Fatalf("Invalid IP address returned: %v", iface.Address())
}
iface := info.Iface()
if iface.Address() != nil && iface.Address().IP.To4() == nil {
t.Fatalf("Invalid IP address returned: %v", iface.Address())
}
if info.Gateway().To4() != nil {
@ -1711,11 +1705,10 @@ func TestEnableIPv6(t *testing.T) {
}
}()
ip, cidrv6, err := net.ParseCIDR("fe80::1/64")
cidrv6, err := types.ParseCIDR("fe80::1/64")
if err != nil {
t.Fatal(err)
}
cidrv6.IP = ip
netOption := options.Generic{
netlabel.EnableIPv6: true,

View file

@ -38,6 +38,9 @@ const (
// OverlayNeighborIP constant represents overlay driver neighbor IP
OverlayNeighborIP = DriverPrefix + ".overlay.neighbor_ip"
// Gateway represents the gateway for the network
Gateway = Prefix + ".gateway"
)
// Key extracts the key portion of the label

View file

@ -55,22 +55,94 @@ type EndpointWalker func(ep Endpoint) bool
type svcMap map[string]net.IP
// IpamConf contains all the ipam related configurations for a network
type IpamConf struct {
PreferredPool string
SubPool string
Options map[string]string // IPAM input options
IsV6 bool
Gateway string
AuxAddresses map[string]string
}
// Validate checks whether the configuration is valid
func (c *IpamConf) Validate() error {
if c.Gateway != "" && nil == net.ParseIP(c.Gateway) {
return types.BadRequestErrorf("invalid gateway address %s in IpamConf dtructure", c.Gateway)
}
return nil
}
// IpamInfo contains all the ipam related operational info for a network
type IpamInfo struct {
PoolID string
Meta map[string]string
driverapi.IPAMData
}
// MarshalJSON encodes IpamInfo into json message
func (i *IpamInfo) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{
"PoolID": i.PoolID,
}
v, err := json.Marshal(&i.IPAMData)
if err != nil {
return nil, err
}
m["IPAMData"] = string(v)
if i.Meta != nil {
m["Meta"] = i.Meta
}
return json.Marshal(m)
}
// UnmarshalJSON decodes json message into PoolData
func (i *IpamInfo) UnmarshalJSON(data []byte) error {
var (
m map[string]interface{}
err error
)
if err = json.Unmarshal(data, &m); err != nil {
return err
}
i.PoolID = m["PoolID"].(string)
if v, ok := m["Meta"]; ok {
b, _ := json.Marshal(v)
if err = json.Unmarshal(b, &i.Meta); err != nil {
return err
}
}
if v, ok := m["IPAMData"]; ok {
if err = json.Unmarshal([]byte(v.(string)), &i.IPAMData); err != nil {
return err
}
}
return nil
}
type network struct {
ctrlr *controller
name string
networkType string
id string
driver driverapi.Driver
enableIPv6 bool
endpointCnt uint64
endpoints endpointTable
generic options.Generic
dbIndex uint64
svcRecords svcMap
dbExists bool
persist bool
stopWatchCh chan struct{}
dataScope datastore.DataScope
ctrlr *controller
name string
networkType string
id string
ipamType string
driver driverapi.Driver
addrSpace string
ipamV4Config []*IpamConf
ipamV6Config []*IpamConf
ipamV4Info []*IpamInfo
ipamV6Info []*IpamInfo
enableIPv6 bool
endpointCnt uint64
endpoints endpointTable
generic options.Generic
dbIndex uint64
svcRecords svcMap
dbExists bool
persist bool
stopWatchCh chan struct{}
dataScope datastore.DataScope
sync.Mutex
}
@ -178,10 +250,42 @@ func (n *network) MarshalJSON() ([]byte, error) {
netMap["name"] = n.name
netMap["id"] = n.id
netMap["networkType"] = n.networkType
netMap["ipamType"] = n.ipamType
netMap["addrSpace"] = n.addrSpace
netMap["endpointCnt"] = n.endpointCnt
netMap["enableIPv6"] = n.enableIPv6
netMap["generic"] = n.generic
if n.generic != nil {
netMap["generic"] = n.generic
}
netMap["persist"] = n.persist
if len(n.ipamV4Config) > 0 {
ics, err := json.Marshal(n.ipamV4Config)
if err != nil {
return nil, err
}
netMap["ipamV4Config"] = string(ics)
}
if len(n.ipamV4Info) > 0 {
iis, err := json.Marshal(n.ipamV4Info)
if err != nil {
return nil, err
}
netMap["ipamV4Info"] = string(iis)
}
if len(n.ipamV6Config) > 0 {
ics, err := json.Marshal(n.ipamV6Config)
if err != nil {
return nil, err
}
netMap["ipamV6Config"] = string(ics)
}
if len(n.ipamV6Info) > 0 {
iis, err := json.Marshal(n.ipamV6Info)
if err != nil {
return nil, err
}
netMap["ipamV6Info"] = string(iis)
}
return json.Marshal(netMap)
}
@ -193,14 +297,36 @@ func (n *network) UnmarshalJSON(b []byte) (err error) {
}
n.name = netMap["name"].(string)
n.id = netMap["id"].(string)
n.ipamType = netMap["ipamType"].(string)
n.addrSpace = netMap["addrSpace"].(string)
n.networkType = netMap["networkType"].(string)
n.endpointCnt = uint64(netMap["endpointCnt"].(float64))
n.enableIPv6 = netMap["enableIPv6"].(bool)
if netMap["generic"] != nil {
n.generic = netMap["generic"].(map[string]interface{})
if v, ok := netMap["generic"]; ok {
n.generic = v.(map[string]interface{})
}
if netMap["persist"] != nil {
n.persist = netMap["persist"].(bool)
if v, ok := netMap["persist"]; ok {
n.persist = v.(bool)
}
if v, ok := netMap["ipamV4Config"]; ok {
if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Config); err != nil {
return err
}
}
if v, ok := netMap["ipamV4Info"]; ok {
if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Info); err != nil {
return err
}
}
if v, ok := netMap["ipamV6Config"]; ok {
if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Config); err != nil {
return err
}
}
if v, ok := netMap["ipamV6Info"]; ok {
if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Info); err != nil {
return err
}
}
return nil
}
@ -228,6 +354,16 @@ func NetworkOptionPersist(persist bool) NetworkOption {
}
}
// NetworkOptionIpam function returns an option setter for the ipam configuration for this network
func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf) NetworkOption {
return func(n *network) {
n.ipamType = ipamDriver
n.addrSpace = addrSpace
n.ipamV4Config = ipV4
n.ipamV6Config = ipV6
}
}
func (n *network) processOptions(options ...NetworkOption) {
for _, opt := range options {
if opt != nil {
@ -239,9 +375,7 @@ func (n *network) processOptions(options ...NetworkOption) {
func (n *network) Delete() error {
var err error
n.Lock()
ctrlr := n.ctrlr
n.Unlock()
ctrlr := n.getController()
ctrlr.Lock()
_, ok := ctrlr.networks[n.id]
@ -278,6 +412,8 @@ func (n *network) Delete() error {
return err
}
n.ipamRelease()
return nil
}
@ -319,7 +455,7 @@ func (n *network) addEndpoint(ep *endpoint) error {
}
}()
err = d.CreateEndpoint(n.id, ep.id, ep, ep.generic)
err = d.CreateEndpoint(n.id, ep.id, ep.Interface(), ep.generic)
if err != nil {
return types.InternalErrorf("failed to create endpoint %s on network %s: %v", ep.Name(), n.Name(), err)
}
@ -338,15 +474,21 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
return nil, types.ForbiddenErrorf("service endpoint with name %s already exists", name)
}
ep := &endpoint{name: name,
generic: make(map[string]interface{})}
ep := &endpoint{name: name, generic: make(map[string]interface{}), iface: &endpointInterface{}}
ep.id = stringid.GenerateRandomID()
ep.network = n
ep.processOptions(options...)
n.Lock()
ctrlr := n.ctrlr
n.Unlock()
if err = ep.assignAddress(); err != nil {
return nil, err
}
defer func() {
if err != nil {
ep.releaseAddress()
}
}()
ctrlr := n.getController()
n.IncEndpointCnt()
if err = ctrlr.updateToStore(n); err != nil {
@ -441,7 +583,7 @@ func (n *network) isGlobalScoped() bool {
func (n *network) updateSvcRecord(ep *endpoint, isAdd bool) {
n.Lock()
var recs []etchosts.Record
if iface := ep.Iface(); iface != nil {
if iface := ep.Iface(); iface.Address() != nil {
if isAdd {
n.svcRecords[ep.Name()] = iface.Address().IP
n.svcRecords[ep.Name()+"."+n.name] = iface.Address().IP
@ -504,3 +646,161 @@ func (n *network) getController() *controller {
defer n.Unlock()
return n.ctrlr
}
func (n *network) ipamAllocate() ([]func(), error) {
var (
cnl []func()
err error
)
// For now also exclude bridge from using new ipam
if n.Type() == "host" || n.Type() == "null" || n.Type() == "bridge" {
return cnl, nil
}
ipam, err := n.getController().getIpamDriver(n.ipamType)
if err != nil {
return nil, err
}
if n.addrSpace == "" {
if n.addrSpace, err = n.deriveAddressSpace(); err != nil {
return nil, err
}
}
if n.ipamV4Config == nil {
n.ipamV4Config = []*IpamConf{&IpamConf{}}
}
n.ipamV4Info = make([]*IpamInfo, len(n.ipamV4Config))
for i, cfg := range n.ipamV4Config {
if err = cfg.Validate(); err != nil {
return nil, err
}
d := &IpamInfo{}
n.ipamV4Info[i] = d
d.PoolID, d.Pool, d.Meta, err = ipam.RequestPool(n.addrSpace, cfg.PreferredPool, cfg.SubPool, cfg.Options, cfg.IsV6)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
if err := ipam.ReleasePool(d.PoolID); err != nil {
log.Warnf("Failed to release address pool %s after failure to create network %s (%s)", d.PoolID, n.Name(), n.ID())
}
}
}()
if gws, ok := d.Meta[netlabel.Gateway]; ok {
if d.Gateway, err = types.ParseCIDR(gws); err != nil {
return nil, types.BadRequestErrorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err)
}
}
// If user requested a specific gateway, libnetwork will allocate it
// irrespective of whether ipam driver returned a gateway already.
// If none of the above is true, libnetwork will allocate one.
if cfg.Gateway != "" || d.Gateway == nil {
if d.Gateway, _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(cfg.Gateway), nil); err != nil {
return nil, types.InternalErrorf("failed to allocate gateway (%v): %v", cfg.Gateway, err)
}
}
cnl = append(cnl, func() {
if err := ipam.ReleaseAddress(d.PoolID, d.Gateway.IP); err != nil {
log.Warnf("Failed to release gw address %s after failure to create network %s (%s)", d.Gateway, n.Name(), n.ID())
}
})
if cfg.AuxAddresses != nil {
var ip net.IP
d.IPAMData.AuxAddresses = make(map[string]*net.IPNet, len(cfg.AuxAddresses))
for k, v := range cfg.AuxAddresses {
if ip = net.ParseIP(v); ip == nil {
return nil, types.BadRequestErrorf("non parsable secondary ip address %s (%s) passed for network %s", k, v, n.Name())
}
if d.IPAMData.AuxAddresses[k], _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(cfg.Gateway), nil); err != nil {
return nil, types.InternalErrorf("failed to allocate secondary ip address %s(%s): %v", k, v, err)
}
}
}
}
return cnl, nil
}
func (n *network) ipamRelease() {
// For now also exclude bridge from using new ipam
if n.Type() == "host" || n.Type() == "null" || n.Type() == "bridge" {
return
}
ipam, err := n.getController().getIpamDriver(n.ipamType)
if err != nil {
log.Warnf("Failed to retrieve ipam driver to release address pool(s) on delete of network %s (%s): %v", n.Name(), n.ID(), err)
return
}
for _, d := range n.ipamV4Info {
if d.Gateway != nil {
if err := ipam.ReleaseAddress(d.PoolID, d.Gateway.IP); err != nil {
log.Warnf("Failed to release gateway ip address %s on delete of network %s (%s): %v", d.Gateway.IP, n.Name(), n.ID(), err)
}
}
if d.IPAMData.AuxAddresses != nil {
for k, nw := range d.IPAMData.AuxAddresses {
if err := ipam.ReleaseAddress(d.PoolID, nw.IP); err != nil {
log.Warnf("Failed to release secondary ip address %s (%v) on delete of network %s (%s): %v", k, nw.IP, n.Name(), n.ID(), err)
}
}
}
if err := ipam.ReleasePool(d.PoolID); err != nil {
log.Warnf("Failed to release address pool %s on delete of network %s (%s): %v", d.PoolID, n.Name(), n.ID(), err)
}
}
}
func (n *network) getIPInfo() []*IpamInfo {
n.Lock()
defer n.Unlock()
l := make([]*IpamInfo, 0, len(n.ipamV4Info))
for _, d := range n.ipamV4Info {
l = append(l, d)
}
return l
}
func (n *network) getIPv4Data() []driverapi.IPAMData {
l := make([]driverapi.IPAMData, 0, len(n.ipamV4Info))
n.Lock()
for _, d := range n.ipamV4Info {
l = append(l, d.IPAMData)
}
n.Unlock()
return l
}
func (n *network) getIPv6Data() []driverapi.IPAMData {
l := make([]driverapi.IPAMData, 0, len(n.ipamV6Info))
n.Lock()
for _, d := range n.ipamV6Info {
l = append(l, d.IPAMData)
}
n.Unlock()
return l
}
func (n *network) deriveAddressSpace() (string, error) {
c := n.getController()
c.Lock()
ipd, ok := c.ipamDrivers[n.ipamType]
c.Unlock()
if !ok {
return "", types.NotFoundErrorf("could not find ipam driver %s to get default address space", n.ipamType)
}
if n.isGlobalScoped() {
return ipd.defaultGlobalAddressSpace, nil
}
return ipd.defaultLocalAddressSpace, nil
}

View file

@ -324,12 +324,12 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error {
i := ep.iface
ep.Unlock()
if i != nil {
if i.srcName != "" {
var ifaceOptions []osl.IfaceOption
ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(&i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes))
if i.addrv6.IP.To16() != nil {
ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().AddressIPv6(&i.addrv6))
ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes))
if i.addrv6 != nil && i.addrv6.IP.To16() != nil {
ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().AddressIPv6(i.addrv6))
}
if err := sb.osSbox.AddInterface(i.srcName, i.dstPrefix, ifaceOptions...); err != nil {

View file

@ -45,14 +45,7 @@ func (c *controller) initGlobalStore() error {
c.Lock()
c.globalStore = store
c.Unlock()
nws, err := c.getNetworksFromStore(true)
if err == nil {
c.processNetworkUpdate(nws, nil)
} else if err != datastore.ErrKeyNotFound {
log.Warnf("failed to read networks from globalstore during init : %v", err)
}
return c.watchNetworks()
return nil
}
func (c *controller) initLocalStore() error {
@ -66,14 +59,37 @@ func (c *controller) initLocalStore() error {
c.Lock()
c.localStore = localStore
c.Unlock()
return nil
}
nws, err := c.getNetworksFromStore(false)
func (c *controller) restoreFromGlobalStore() error {
c.Lock()
s := c.globalStore
c.Unlock()
if s == nil {
return nil
}
c.restore("global")
return c.watchNetworks()
}
func (c *controller) restoreFromLocalStore() error {
c.Lock()
s := c.localStore
c.Unlock()
if s != nil {
c.restore("local")
}
return nil
}
func (c *controller) restore(store string) {
nws, err := c.getNetworksFromStore(store == "global")
if err == nil {
c.processNetworkUpdate(nws, nil)
} else if err != datastore.ErrKeyNotFound {
log.Warnf("failed to read networks from localstore during init : %v", err)
log.Warnf("failed to read networks from %s store during init : %v", store, err)
}
return nil
}
func (c *controller) getNetworksFromStore(global bool) ([]*store.KVPair, error) {

View file

@ -52,7 +52,6 @@ func testLocalBackend(t *testing.T, provider, url string, storeConfig *store.Con
genericOption[netlabel.GenericData] = driverOptions
cfgOptions = append(cfgOptions, config.OptionDriverConfig("host", genericOption))
fmt.Printf("URL : %s\n", url)
ctrl, err := New(cfgOptions...)
if err != nil {
t.Fatalf("Error new controller: %v", err)
@ -139,7 +138,7 @@ func TestLocalStoreLockTimeout(t *testing.T) {
}
defer ctrl1.Stop()
// Use the same boltdb file without closing the previous controller
ctrl2, _ := New(cfgOptions...)
ctrl2, err := New(cfgOptions...)
if err != nil {
t.Fatalf("Error new controller: %v", err)
}

View file

@ -154,6 +154,9 @@ func ParseProtocol(s string) Protocol {
// GetMacCopy returns a copy of the passed MAC address
func GetMacCopy(from net.HardwareAddr) net.HardwareAddr {
if from == nil {
return nil
}
to := make(net.HardwareAddr, len(from))
copy(to, from)
return to
@ -161,6 +164,9 @@ func GetMacCopy(from net.HardwareAddr) net.HardwareAddr {
// GetIPCopy returns a copy of the passed IP address
func GetIPCopy(from net.IP) net.IP {
if from == nil {
return nil
}
to := make(net.IP, len(from))
copy(to, from)
return to
@ -222,23 +228,32 @@ func GetMinimalIPNet(nw *net.IPNet) *net.IPNet {
var v4inV6MaskPrefix = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
// GetHostPartIP returns the host portion of the ip address identified by the mask.
// IP address representation is not modified. If address and mask are not compatible
// an error is returned.
func GetHostPartIP(ip net.IP, mask net.IPMask) (net.IP, error) {
// compareIPMask checks if the passed ip and mask are semantically compatible.
// It returns the byte indexes for the address and mask so that caller can
// do bitwise operations without modifying address representation.
func compareIPMask(ip net.IP, mask net.IPMask) (is int, ms int, err error) {
// Find the effective starting of address and mask
is := 0
ms := 0
if len(ip) == net.IPv6len && ip.To4() != nil {
is = 12
}
if len(ip[is:]) == net.IPv4len && len(mask) == net.IPv6len && bytes.Equal(mask[:12], v4inV6MaskPrefix) {
ms = 12
}
// Check if address and mask are semantically compatible
if len(ip[is:]) != len(mask[ms:]) {
return nil, fmt.Errorf("cannot compute host portion ip address as ip and mask are not compatible: (%#v, %#v)", ip, mask)
err = fmt.Errorf("ip and mask are not compatible: (%#v, %#v)", ip, mask)
}
return
}
// GetHostPartIP returns the host portion of the ip address identified by the mask.
// IP address representation is not modified. If address and mask are not compatible
// an error is returned.
func GetHostPartIP(ip net.IP, mask net.IPMask) (net.IP, error) {
// Find the effective starting of address and mask
is, ms, err := compareIPMask(ip, mask)
if err != nil {
return nil, fmt.Errorf("cannot compute host portion ip address because %s", err)
}
// Compute host portion
@ -250,6 +265,34 @@ func GetHostPartIP(ip net.IP, mask net.IPMask) (net.IP, error) {
return out, nil
}
// GetBroadcastIP returns the broadcast ip address for the passed network (ip and mask).
// IP address representation is not modified. If address and mask are not compatible
// an error is returned.
func GetBroadcastIP(ip net.IP, mask net.IPMask) (net.IP, error) {
// Find the effective starting of address and mask
is, ms, err := compareIPMask(ip, mask)
if err != nil {
return nil, fmt.Errorf("cannot compute broadcast ip address because %s", err)
}
// Compute broadcast address
out := GetIPCopy(ip)
for i := 0; i < len(mask[ms:]); i++ {
out[is+i] |= ^mask[ms+i]
}
return out, nil
}
// ParseCIDR returns the *net.IPNet represented by the passed CIDR notation
func ParseCIDR(cidr string) (n *net.IPNet, e error) {
var i net.IP
if i, n, e = net.ParseCIDR(cidr); e == nil {
n.IP = i
}
return
}
const (
// NEXTHOP indicates a StaticRoute with an IP next hop.
NEXTHOP = iota

View file

@ -111,6 +111,75 @@ func TestErrorConstructors(t *testing.T) {
}
}
func TestCompareIPMask(t *testing.T) {
input := []struct {
ip net.IP
mask net.IPMask
is int
ms int
isErr bool
}{
{ // ip in v4Inv6 representation, mask in v4 representation
ip: net.IPv4(172, 28, 30, 1),
mask: []byte{0xff, 0xff, 0xff, 0},
is: 12,
ms: 0,
},
{ // ip and mask in v4Inv6 representation
ip: net.IPv4(172, 28, 30, 2),
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
is: 12,
ms: 12,
},
{ // ip in v4 representation, mask in v4Inv6 representation
ip: net.IPv4(172, 28, 30, 3)[12:],
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
is: 0,
ms: 12,
},
{ // ip and mask in v4 representation
ip: net.IPv4(172, 28, 30, 4)[12:],
mask: []byte{0xff, 0xff, 0xff, 0},
is: 0,
ms: 0,
},
{ // ip and mask as v6
ip: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"),
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
is: 0,
ms: 0,
},
{
ip: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"),
mask: []byte{0xff, 0xff, 0xff, 0},
isErr: true,
},
{
ip: net.ParseIP("173.32.4.5"),
mask: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0},
isErr: true,
},
{
ip: net.ParseIP("173.32.4.5"),
mask: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
isErr: true,
},
}
for ind, i := range input {
is, ms, err := compareIPMask(i.ip, i.mask)
if i.isErr {
if err == nil {
t.Fatalf("Incorrect error condition for element %d. is: %d, ms: %d, err: %v", ind, is, ms, err)
}
} else {
if i.is != is || i.ms != ms {
t.Fatalf("expected is: %d, ms: %d. Got is: %d, ms: %d for element %d", i.is, i.ms, is, ms, ind)
}
}
}
}
func TestUtilGetHostPortionIP(t *testing.T) {
input := []struct {
ip net.IP
@ -168,3 +237,106 @@ func TestUtilGetHostPortionIP(t *testing.T) {
t.Fatalf("Unexpected success")
}
}
func TestUtilGetBroadcastIP(t *testing.T) {
input := []struct {
ip net.IP
mask net.IPMask
bcast net.IP
err error
}{
// ip in v4Inv6 representation, mask in v4 representation
{
ip: net.IPv4(172, 28, 30, 1),
mask: []byte{0xff, 0xff, 0xff, 0},
bcast: net.IPv4(172, 28, 30, 255),
},
{
ip: net.IPv4(10, 28, 30, 1),
mask: []byte{0xff, 0, 0, 0},
bcast: net.IPv4(10, 255, 255, 255),
},
// ip and mask in v4Inv6 representation
{
ip: net.IPv4(172, 28, 30, 2),
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
bcast: net.IPv4(172, 28, 30, 255),
},
{
ip: net.IPv4(172, 28, 30, 2),
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0},
bcast: net.IPv4(172, 28, 255, 255),
},
// ip in v4 representation, mask in v4Inv6 representation
{
ip: net.IPv4(172, 28, 30, 3)[12:],
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
bcast: net.IPv4(172, 28, 30, 255)[12:],
},
{
ip: net.IPv4(172, 28, 30, 3)[12:],
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
bcast: net.IPv4(172, 255, 255, 255)[12:],
},
// ip and mask in v4 representation
{
ip: net.IPv4(172, 28, 30, 4)[12:],
mask: []byte{0xff, 0xff, 0xff, 0},
bcast: net.IPv4(172, 28, 30, 255)[12:],
},
{
ip: net.IPv4(172, 28, 30, 4)[12:],
mask: []byte{0xff, 0xff, 0, 0},
bcast: net.IPv4(172, 28, 255, 255)[12:],
},
{ // ip and mask as v6
ip: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"),
mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
bcast: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEFF:FFFF"),
},
}
for _, i := range input {
h, err := GetBroadcastIP(i.ip, i.mask)
if err != nil {
t.Fatal(err)
}
if !i.bcast.Equal(h) {
t.Fatalf("Failed to return expected host ip. Expected: %s. Got: %s", i.bcast, h)
}
}
// ip as v6 and mask as v4 are not compatible
if _, err := GetBroadcastIP(net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"), []byte{0xff, 0xff, 0xff, 0}); err == nil {
t.Fatalf("Unexpected success")
}
// ip as v4 and non conventional mask
if _, err := GetBroadcastIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0}); err == nil {
t.Fatalf("Unexpected success")
}
// ip as v4 and non conventional mask
if _, err := GetBroadcastIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}); err == nil {
t.Fatalf("Unexpected success")
}
}
func TestParseCIDR(t *testing.T) {
input := []struct {
cidr string
ipnw *net.IPNet
}{
{"192.168.22.44/16", &net.IPNet{IP: net.IP{192, 168, 22, 44}, Mask: net.IPMask{255, 255, 0, 0}}},
{"10.10.2.0/24", &net.IPNet{IP: net.IP{10, 10, 2, 0}, Mask: net.IPMask{255, 255, 255, 0}}},
{"10.0.0.100/17", &net.IPNet{IP: net.IP{10, 0, 0, 100}, Mask: net.IPMask{255, 255, 128, 0}}},
}
for _, i := range input {
nw, err := ParseCIDR(i.cidr)
if err != nil {
t.Fatal(err)
}
if !CompareIPNet(nw, i.ipnw) {
t.Fatalf("network differ. Expected %v. Got: %v", i.ipnw, nw)
}
}
}