Compare commits
25 Commits
freebsd-co
...
release-0.
Author | SHA1 | Date |
---|---|---|
aboch | c6a0ee8aaf | |
Jana Radhakrishnan | 448706c9d4 | |
Jana Radhakrishnan | b176a99c56 | |
Jana Radhakrishnan | c062be3842 | |
Jana Radhakrishnan | 07880789c9 | |
Alessandro Boch | 5a193ee895 | |
Alessandro Boch | c6584c6fbd | |
Alexander Morozov | c87febef71 | |
Jana Radhakrishnan | 38cb40c4d4 | |
Madhu Venugopal | 8da00fe824 | |
Jana Radhakrishnan | 8f5e434276 | |
aboch | 1c109b7c65 | |
Jana Radhakrishnan | 17e0f317d7 | |
Alessandro Boch | a3a2367267 | |
David Manouchehri | 7311b28766 | |
Alessandro Boch | 76f6e0898b | |
Madhu Venugopal | f238be9000 | |
Alessandro Boch | 8ac3b97ddf | |
Alexander Morozov | e155f4eecc | |
Alessandro Boch | 1c123c389b | |
Alessandro Boch | 3eb77fb970 | |
Zhang Wei | 2652fb7fed | |
Alex Chan | 71caa40f7a | |
Alessandro Boch | 4b287a616a | |
Jana Radhakrishnan | 7949ca05b5 |
|
@ -250,8 +250,12 @@ func (h *Handle) set(ordinal, start, end uint64, any bool, release bool) (uint64
|
|||
)
|
||||
|
||||
for {
|
||||
if h.store != nil {
|
||||
if err := h.store.GetObject(datastore.Key(h.Key()...), h); err != nil && err != datastore.ErrKeyNotFound {
|
||||
var store datastore.DataStore
|
||||
h.Lock()
|
||||
store = h.store
|
||||
h.Unlock()
|
||||
if store != nil {
|
||||
if err := store.GetObject(datastore.Key(h.Key()...), h); err != nil && err != datastore.ErrKeyNotFound {
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
|
@ -552,7 +556,7 @@ func pushReservation(bytePos, bitPos uint64, head *sequence, release bool) *sequ
|
|||
}
|
||||
removeCurrentIfEmpty(&newHead, newSequence, current)
|
||||
mergeSequences(previous)
|
||||
} else if precBlocks == current.count-2 { // Last in sequence (B)
|
||||
} else if precBlocks == current.count { // Last in sequence (B)
|
||||
newSequence.next = current.next
|
||||
current.next = newSequence
|
||||
mergeSequences(current)
|
||||
|
|
|
@ -352,6 +352,62 @@ func TestPushReservation(t *testing.T) {
|
|||
{&sequence{block: 0xffff0000, count: 7}, 25, 7, &sequence{block: 0xffff0000, count: 7}},
|
||||
{&sequence{block: 0xffffffff, count: 7, next: &sequence{block: 0xfffffffe, count: 1, next: &sequence{block: 0xffffffff, count: 1}}}, 7, 7,
|
||||
&sequence{block: 0xffffffff, count: 7, next: &sequence{block: 0xfffffffe, count: 1, next: &sequence{block: 0xffffffff, count: 1}}}},
|
||||
|
||||
// Set last bit
|
||||
{&sequence{block: 0x0, count: 8}, 31, 7, &sequence{block: 0x0, count: 7, next: &sequence{block: 0x1, count: 1}}},
|
||||
|
||||
// Set bit in a middle sequence in the first block, first bit
|
||||
{&sequence{block: 0x40000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 4, 0,
|
||||
&sequence{block: 0x40000000, count: 1, next: &sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5,
|
||||
next: &sequence{block: 0x1, count: 1}}}}},
|
||||
|
||||
// Set bit in a middle sequence in the first block, first bit (merge involved)
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 4, 0,
|
||||
&sequence{block: 0x80000000, count: 2, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x1, count: 1}}}},
|
||||
|
||||
// Set bit in a middle sequence in the first block, last bit
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 4, 31,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x1, count: 1, next: &sequence{block: 0x0, count: 5,
|
||||
next: &sequence{block: 0x1, count: 1}}}}},
|
||||
|
||||
// Set bit in a middle sequence in the first block, middle bit
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 4, 16,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x8000, count: 1, next: &sequence{block: 0x0, count: 5,
|
||||
next: &sequence{block: 0x1, count: 1}}}}},
|
||||
|
||||
// Set bit in a middle sequence in a middle block, first bit
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 16, 0,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 3, next: &sequence{block: 0x80000000, count: 1,
|
||||
next: &sequence{block: 0x0, count: 2, next: &sequence{block: 0x1, count: 1}}}}}},
|
||||
|
||||
// Set bit in a middle sequence in a middle block, last bit
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 16, 31,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 3, next: &sequence{block: 0x1, count: 1,
|
||||
next: &sequence{block: 0x0, count: 2, next: &sequence{block: 0x1, count: 1}}}}}},
|
||||
|
||||
// Set bit in a middle sequence in a middle block, middle bit
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 16, 15,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 3, next: &sequence{block: 0x10000, count: 1,
|
||||
next: &sequence{block: 0x0, count: 2, next: &sequence{block: 0x1, count: 1}}}}}},
|
||||
|
||||
// Set bit in a middle sequence in the last block, first bit
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 24, 0,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x80000000, count: 1,
|
||||
next: &sequence{block: 0x1, count: 1}}}}},
|
||||
|
||||
// Set bit in a middle sequence in the last block, last bit
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x4, count: 1}}}, 24, 31,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x1, count: 1,
|
||||
next: &sequence{block: 0x4, count: 1}}}}},
|
||||
|
||||
// Set bit in a middle sequence in the last block, last bit (merge involved)
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 24, 31,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x1, count: 2}}}},
|
||||
|
||||
// Set bit in a middle sequence in the last block, middle bit
|
||||
{&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 6, next: &sequence{block: 0x1, count: 1}}}, 24, 16,
|
||||
&sequence{block: 0x80000000, count: 1, next: &sequence{block: 0x0, count: 5, next: &sequence{block: 0x8000, count: 1,
|
||||
next: &sequence{block: 0x1, count: 1}}}}},
|
||||
}
|
||||
|
||||
for n, i := range input {
|
||||
|
@ -635,3 +691,58 @@ func TestSetInRange(t *testing.T) {
|
|||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This one tests an allocation pattern which unveiled an issue in pushReservation
|
||||
// Specifically a failure in detecting when we are in the (B) case (the bit to set
|
||||
// belongs to the last block of the current sequence). Because of a bug, code
|
||||
// was assuming the bit belonged to a block in the middle of the current sequence.
|
||||
// Which in turn caused an incorrect allocation when requesting a bit which is not
|
||||
// in the first or last sequence block.
|
||||
func TestSetAnyInRange(t *testing.T) {
|
||||
numBits := uint64(8 * blockLen)
|
||||
hnd, err := NewHandle("", nil, "", numBits)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := hnd.Set(0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := hnd.Set(255); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
o, err := hnd.SetAnyInRange(128, 255)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != 128 {
|
||||
t.Fatalf("Unexpected ordinal: %d", o)
|
||||
}
|
||||
|
||||
o, err = hnd.SetAnyInRange(128, 255)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if o != 129 {
|
||||
t.Fatalf("Unexpected ordinal: %d", o)
|
||||
}
|
||||
|
||||
o, err = hnd.SetAnyInRange(246, 255)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != 246 {
|
||||
t.Fatalf("Unexpected ordinal: %d", o)
|
||||
}
|
||||
|
||||
o, err = hnd.SetAnyInRange(246, 255)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != 247 {
|
||||
t.Fatalf("Unexpected ordinal: %d", o)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,14 +128,11 @@ type ipamData struct {
|
|||
|
||||
type driverTable map[string]*driverData
|
||||
|
||||
//type networkTable map[string]*network
|
||||
//type endpointTable map[string]*endpoint
|
||||
type ipamTable map[string]*ipamData
|
||||
type sandboxTable map[string]*sandbox
|
||||
|
||||
type controller struct {
|
||||
id string
|
||||
//networks networkTable
|
||||
id string
|
||||
drivers driverTable
|
||||
ipamDrivers ipamTable
|
||||
sandboxes sandboxTable
|
||||
|
|
189
docs/ipam.md
189
docs/ipam.md
|
@ -1,17 +1,17 @@
|
|||
# Ipam Driver
|
||||
# IPAM Driver
|
||||
|
||||
During the Network and Endpoints lifecyle the CNM model controls the ip address assignment for network and endpoint interfaces via the IPAM driver(s).
|
||||
Libnetwork has a default, built-in ipam driver and allows third party ipam drivers to be dinamically plugged. On network creation user can specify which ipam driver libnetwork needs to use for the network's ip address management. Current document explains the APIs with which IPAM driver needs to comply and the corresponding HTTPS request/response body relevant for remote drivers.
|
||||
During the Network and Endpoints lifecyle, the CNM model controls the IP address assignment for network and endpoint interfaces via the IPAM driver(s).
|
||||
Libnetwork has a default, built-in IPAM driver and allows third party IPAM drivers to be dynamically plugged. On network creation, the user can specify which IPAM driver libnetwork needs to use for the network's IP address management. This document explains the APIs with which the IPAM driver needs to comply, and the corresponding HTTPS request/response body relevant for remote drivers.
|
||||
|
||||
|
||||
## Remote Ipam driver
|
||||
## Remote IPAM driver
|
||||
|
||||
On the same line of remote network driver registration (see [remote.md] for more details), Libnetwork initializes the `ipams.remote` package with the `Init()` function, it passes a `ipamapi.Callback` as a parameter, which implements `RegisterOpamDriver()`. The remote driver package uses this interface to register remote drivers with Libnetwork's `NetworkController`, by supplying it in a `plugins.Handle` callback. The remote drivers register and communicate with libnetwork via the Docker plugin package. The `ipams.remote` provides the proxy for the remote driver processes.
|
||||
On the same line of remote network driver registration (see [remote.md](./remote.md) for more details), libnetwork initializes the `ipams.remote` package with the `Init()` function. It passes a `ipamapi.Callback` as a parameter, which implements `RegisterIpamDriver()`. The remote driver package uses this interface to register remote drivers with libnetwork's `NetworkController`, by supplying it in a `plugins.Handle` callback. The remote drivers register and communicate with libnetwork via the Docker plugin package. The `ipams.remote` provides the proxy for the remote driver processes.
|
||||
|
||||
|
||||
## Protocol
|
||||
|
||||
Communication protocol is same as remote network driver.
|
||||
Communication protocol is the same as the remote network driver.
|
||||
|
||||
## Handshake
|
||||
|
||||
|
@ -20,93 +20,100 @@ More detailed information can be found in the respective section in this documen
|
|||
|
||||
## Datastore Requirements
|
||||
|
||||
It is remote driver responsibility to manage its database.
|
||||
It is the remote driver's responsibility to manage its database.
|
||||
|
||||
## Ipam Contract
|
||||
|
||||
The ipam driver (internal or remote) has to comply with the contract specified in `ipamapi.contract.go`:
|
||||
The IPAM driver (internal or remote) has to comply with the contract specified in `ipamapi.contract.go`:
|
||||
|
||||
```go
|
||||
// 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 addresses 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
|
||||
}
|
||||
```
|
||||
|
||||
The following sections explain the each of the above API's semantics, when they are called during network/endpoint lifecycle, and the corresponding payload for remote driver HTTP request/responses.
|
||||
|
||||
|
||||
// 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 addresses 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
|
||||
}
|
||||
## IPAM Configuration and flow
|
||||
|
||||
The following sections explain the each of the above API's semantic, when they are called during network/endpoint lyfecyle and the correspondent payload for remote driver HTTP request/responses.
|
||||
A libnetwork user can provide IPAM related configuration when creating a network, via the `NetworkOptionIpam` setter function.
|
||||
|
||||
```go
|
||||
func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf) NetworkOption
|
||||
```
|
||||
|
||||
## Ipam Configuration and flow
|
||||
|
||||
Libnetwork user can provide ipam related configuration when creating a network, via the `NetworkOptionIpam` setter function.
|
||||
|
||||
`func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf) NetworkOption`
|
||||
|
||||
Caller has to provide the ipam driver name and may provide the address space and a list of `IpamConf` structures for ipv4 and a list for ipv6. The ipam driver name is the only mandatory field. If not provided, network creation will fail.
|
||||
The caller has to provide the IPAM driver name and may provide the address space and a list of `IpamConf` structures for IPv4 and a list for IPv6. The IPAM driver name is the only mandatory field. If not provided, network creation will fail.
|
||||
|
||||
In the list of configurations, each element has the following form:
|
||||
|
||||
```go
|
||||
// IpamConf contains all the ipam related configurations for a network
|
||||
type IpamConf struct {
|
||||
// The master address pool for containers and network interfaces
|
||||
PreferredPool string
|
||||
// A subset of the master pool. If specified,
|
||||
// this becomes the container pool
|
||||
SubPool string
|
||||
// Input options for IPAM Driver (optional)
|
||||
Options map[string]string
|
||||
// Preferred Network Gateway address (optional)
|
||||
Gateway string
|
||||
// Auxiliary addresses for network driver. Must be within the master pool.
|
||||
// libnetwork will reserve them if they fall into the container pool
|
||||
AuxAddresses map[string]string
|
||||
}
|
||||
```
|
||||
|
||||
// IpamConf contains all the ipam related configurations for a network
|
||||
type IpamConf struct {
|
||||
// The master address pool for containers and network interfaces
|
||||
PreferredPool string
|
||||
// A subset of the master pool. If specified,
|
||||
// this becomes the container pool
|
||||
SubPool string
|
||||
// Input options for IPAM Driver (optional)
|
||||
Options map[string]string
|
||||
// Preferred Network Gateway address (optional)
|
||||
Gateway string
|
||||
// Auxiliary addresses for network driver. Must be within the master pool.
|
||||
// libnetwork will reserve them if they fall into the container pool
|
||||
AuxAddresses map[string]string
|
||||
}
|
||||
On network creation, libnetwork will iterate the list and perform the following requests to the IPAM driver:
|
||||
|
||||
1. Request the address pool and pass the options along via `RequestPool()`.
|
||||
2. Request the network gateway address if specified. Otherwise request any address from the pool to be used as network gateway. This is done via `RequestAddress()`.
|
||||
3. Request each of the specified auxiliary addresses via `RequestAddress()`.
|
||||
|
||||
On network creation, libnetwork will iterate the list and perform the following requests to ipam driver:
|
||||
1) Request the address pool and pass the options along via `RequestPool()`.
|
||||
2) Request the network gateway address if specified. Otherwise request any address from the pool to be used as network gateway. This is done via `RequestAddress()`.
|
||||
3) Request each of the specified auxiliary addresses via `RequestAddress()`.
|
||||
|
||||
If the list of ipv4 configurations is empty, libnetwork will automatically add one empty `IpamConf` structure. This will cause libnetwork to request ipam driver an ipv4 address pool of the driver choice on the configured address space, if specified, or on the ipam driver default address space otherwise. If the ipam driver is not able to provide an address pool, network creation will fail.
|
||||
If the list of ipv6 configurations is empty, libnetwork will not take any action.
|
||||
The data retrieved from the ipam driver during the execution of point 1) to 3) will be stored in the network structure as a list of `IpamInfo` structures for IPv6 and for IPv6.
|
||||
If the list of IPv4 configurations is empty, libnetwork will automatically add one empty `IpamConf` structure. This will cause libnetwork to request IPAM driver an IPv4 address pool of the driver's choice on the configured address space, if specified, or on the IPAM driver default address space otherwise. If the IPAM driver is not able to provide an address pool, network creation will fail.
|
||||
If the list of IPv6 configurations is empty, libnetwork will not take any action.
|
||||
The data retrieved from the IPAM driver during the execution of point 1) to 3) will be stored in the network structure as a list of `IpamInfo` structures for IPv6 and for IPv6.
|
||||
|
||||
On endpoint creation, libnetwork will iterate over the list of configs and perform the following operation:
|
||||
1) Request an IPv4 address from the ipv4 pool and assign it to the endpoint interface ipv4 address. If successful, stop iterating.
|
||||
2) Request an IPv6 address from the ipv6 pool (if exists) and assign it to the endpoint interface ipv6 address. If successful, stop iterating.
|
||||
|
||||
1. Request an IPv4 address from the IPv4 pool and assign it to the endpoint interface IPv4 address. If successful, stop iterating.
|
||||
2. Request an IPv6 address from the IPv6 pool (if exists) and assign it to the endpoint interface IPv6 address. If successful, stop iterating.
|
||||
|
||||
Endpoint creation will fail if any of the above operation does not succeed
|
||||
|
||||
On endpoint deletion, libnetwork will perform the following operations:
|
||||
1) Release the endpoint interface IPv4 address
|
||||
2) Release the endpoint interface IPv6 address if present
|
||||
|
||||
On Network deletion libnetwork will iterate the list of `IpamData` structures and perform the following requests to ipam driver:
|
||||
1) Release the network gateway address via `ReleaseAddress()`
|
||||
2) Release each of the auxiliary addresses via `ReleaseAddress()`
|
||||
3) Release the pool via `ReleasePool()`
|
||||
1. Release the endpoint interface IPv4 address
|
||||
2. Release the endpoint interface IPv6 address if present
|
||||
|
||||
On network deletion, libnetwork will iterate the list of `IpamData` structures and perform the following requests to ipam driver:
|
||||
|
||||
1. Release the network gateway address via `ReleaseAddress()`
|
||||
2. Release each of the auxiliary addresses via `ReleaseAddress()`
|
||||
3. Release the pool via `ReleasePool()`
|
||||
|
||||
### GetDefaultAddressSpaces
|
||||
|
||||
GetDefaultAddressSpaces returns the default local and global address space names for this ipam. An address space is a set of non-overlapping address pools isolated from other address spaces' pools. In other words, same pool can exist on N different address spaces. An address space naturally maps to a tenant name.
|
||||
In libnetwork the meaning associated to `local` or `global` address space is that a local address space doesn't need to get synchronized across the
|
||||
cluster where as the global address spaces does. Unless specified otherwise in the ipam configuration, libnetwork will request address pools from the default local or default global address space based on the scope of the network being created. For example, if not specified otherwise in the configuration, libnetwork will request address pool from the default local address space for a bridge network, whereas from the default global address space for an overlay network.
|
||||
GetDefaultAddressSpaces returns the default local and global address space names for this IPAM. An address space is a set of non-overlapping address pools isolated from other address spaces' pools. In other words, same pool can exist on N different address spaces. An address space naturally maps to a tenant name.
|
||||
In libnetwork, the meaning associated to `local` or `global` address space is that a local address space doesn't need to get synchronized across the
|
||||
cluster whereas the global address spaces does. Unless specified otherwise in the IPAM configuration, libnetwork will request address pools from the default local or default global address space based on the scope of the network being created. For example, if not specified otherwise in the configuration, libnetwork will request address pool from the default local address space for a bridge network, whereas from the default global address space for an overlay network.
|
||||
|
||||
During registration, the remote driver will receive a POST message to the URL `/IpamDriver.GetDefaultAddressSpaces` with no payload. The driver's response should have the form:
|
||||
|
||||
|
@ -120,10 +127,12 @@ During registration, the remote driver will receive a POST message to the URL `/
|
|||
|
||||
### RequestPool
|
||||
|
||||
This API is for registering a address pool with the ipam driver. Multiple identical calls must return the same result.
|
||||
it is ipam driver responsibility to keep a reference count for the pool.
|
||||
This API is for registering a address pool with the IPAM driver. Multiple identical calls must return the same result.
|
||||
It is the IPAM driver's responsibility to keep a reference count for the pool.
|
||||
|
||||
`RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error)`
|
||||
```go
|
||||
RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error)
|
||||
```
|
||||
|
||||
|
||||
For this API, the remote driver will receive a POST message to the URL `/IpamDriver.RequestPool` with the following payload:
|
||||
|
@ -138,14 +147,15 @@ For this API, the remote driver will receive a POST message to the URL `/IpamDri
|
|||
|
||||
|
||||
Where:
|
||||
* `AddressSpace` the ip address space
|
||||
|
||||
* `AddressSpace` the IP address space
|
||||
* `Pool` The IPv4 or IPv6 address pool in CIDR format
|
||||
* `SubPool` An optional subset of the address pool, an ip range in CIDR format
|
||||
* `Options` A map of ipam driver specific options
|
||||
* `V6` Whether a ipam self chosen pool should be IPv6
|
||||
* `Options` A map of IPAM driver specific options
|
||||
* `V6` Whether an IPAM self-chosen pool should be IPv6
|
||||
|
||||
Address space is the only mandatory field. If no `Pool` is specified ipam driver may return a self chosen address pool. In such case, `V6` flag must be set if caller wants an ipam chosen IPv6 pool. A request with empty `Pool` and non-empty `SubPool` should be rejected as invalid.
|
||||
If a Pool is not specified IPAM will allocate one of the default pools. When the Pool is not specified V6 flag should be set if the network needs IPv6 addresses to be allocated.
|
||||
AddressSpace is the only mandatory field. If no `Pool` is specified IPAM driver may return a self chosen address pool. In such case, `V6` flag must be set if caller wants an IPAM-chosen IPv6 pool. A request with empty `Pool` and non-empty `SubPool` should be rejected as invalid.
|
||||
If a `Pool` is not specified IPAM will allocate one of the default pools. When `Pool` is not specified, the `V6` flag should be set if the network needs IPv6 addresses to be allocated.
|
||||
|
||||
A successful response is in the form:
|
||||
|
||||
|
@ -158,16 +168,19 @@ A successful response is in the form:
|
|||
|
||||
|
||||
Where:
|
||||
|
||||
* `PoolID` is an identifier for this pool. Same pools must have same pool id.
|
||||
* `Pool` is the pool in CIDR format
|
||||
* `Data` is the ipam driver supplied metadata for this pool
|
||||
* `Data` is the IPAM driver supplied metadata for this pool
|
||||
|
||||
|
||||
### ReleasePool
|
||||
|
||||
This API is for releasing a previously registered address pool.
|
||||
|
||||
`ReleasePool(poolID string) error`
|
||||
```go
|
||||
ReleasePool(poolID string) error
|
||||
```
|
||||
|
||||
For this API, the remote driver will receive a POST message to the URL `/IpamDriver.ReleasePool` with the following payload:
|
||||
|
||||
|
@ -176,6 +189,7 @@ For this API, the remote driver will receive a POST message to the URL `/IpamDri
|
|||
}
|
||||
|
||||
Where:
|
||||
|
||||
* `PoolID` is the pool identifier
|
||||
|
||||
A successful response is empty:
|
||||
|
@ -186,7 +200,9 @@ A successful response is empty:
|
|||
|
||||
This API is for reserving an ip address.
|
||||
|
||||
`RequestAddress(string, net.IP, map[string]string) (*net.IPNet, map[string]string, error)`
|
||||
```go
|
||||
RequestAddress(string, net.IP, map[string]string) (*net.IPNet, map[string]string, error)
|
||||
```
|
||||
|
||||
For this API, the remote driver will receive a POST message to the URL `/IpamDriver.RequestAddress` with the following payload:
|
||||
|
||||
|
@ -197,9 +213,10 @@ For this API, the remote driver will receive a POST message to the URL `/IpamDri
|
|||
}
|
||||
|
||||
Where:
|
||||
|
||||
* `PoolID` is the pool identifier
|
||||
* `Address` is the preferred address in regular IP form (A.B.C.D). If empty, the ipam driver chooses any available address on the pool
|
||||
* `Options` are ipam driver specific options
|
||||
* `Address` is the preferred address in regular IP form (A.B.C.D). If empty, the IPAM driver chooses any available address on the pool
|
||||
* `Options` are IPAM driver specific options
|
||||
|
||||
|
||||
A successful response is in the form:
|
||||
|
@ -212,8 +229,9 @@ A successful response is in the form:
|
|||
|
||||
|
||||
Where:
|
||||
|
||||
* `Address` is the allocated address in CIDR format (A.B.C.D/MM)
|
||||
* `Data` is some ipam driver specific metadata
|
||||
* `Data` is some IPAM driver specific metadata
|
||||
|
||||
### ReleaseAddress
|
||||
|
||||
|
@ -227,6 +245,7 @@ For this API, the remote driver will receive a POST message to the URL `/IpamDri
|
|||
}
|
||||
|
||||
Where:
|
||||
* `PoolID` is the pool identifier
|
||||
* `Address` is the ip address to release
|
||||
|
||||
* `PoolID` is the pool identifier
|
||||
* `Address` is the IP address to release
|
||||
|
||||
|
|
|
@ -450,6 +450,8 @@ func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []d
|
|||
}
|
||||
|
||||
if len(ipamV6Data) > 0 {
|
||||
c.AddressIPv6 = ipamV6Data[0].Pool
|
||||
|
||||
if ipamV6Data[0].Gateway != nil {
|
||||
c.AddressIPv6 = types.GetIPNetCopy(ipamV6Data[0].Gateway)
|
||||
}
|
||||
|
@ -738,7 +740,9 @@ func (d *driver) DeleteNetwork(nid string) error {
|
|||
|
||||
// We only delete the bridge when it's not the default bridge. This is keep the backward compatible behavior.
|
||||
if !config.DefaultBridge {
|
||||
err = netlink.LinkDel(n.bridge.Link)
|
||||
if err := netlink.LinkDel(n.bridge.Link); err != nil {
|
||||
logrus.Warnf("Failed to remove bridge interface %s on network %s delete: %v", config.BridgeName, nid, err)
|
||||
}
|
||||
}
|
||||
|
||||
return d.storeDelete(config)
|
||||
|
@ -959,13 +963,20 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
|
|||
if endpoint.addrv6 == nil && config.EnableIPv6 {
|
||||
var ip6 net.IP
|
||||
network := n.bridge.bridgeIPv6
|
||||
if config.AddressIPv6 != nil {
|
||||
network = config.AddressIPv6
|
||||
}
|
||||
|
||||
ones, _ := network.Mask.Size()
|
||||
if ones <= 80 {
|
||||
ip6 = make(net.IP, len(network.IP))
|
||||
copy(ip6, network.IP)
|
||||
for i, h := range endpoint.macAddress {
|
||||
ip6[i+10] = h
|
||||
}
|
||||
if ones > 80 {
|
||||
err = types.ForbiddenErrorf("Cannot self generate an IPv6 address on network %v: At least 48 host bits are needed.", network)
|
||||
return err
|
||||
}
|
||||
|
||||
ip6 = make(net.IP, len(network.IP))
|
||||
copy(ip6, network.IP)
|
||||
for i, h := range endpoint.macAddress {
|
||||
ip6[i+10] = h
|
||||
}
|
||||
|
||||
endpoint.addrv6 = &net.IPNet{IP: ip6, Mask: network.Mask}
|
||||
|
@ -1037,9 +1048,8 @@ func (d *driver) DeleteEndpoint(nid, eid string) error {
|
|||
// Remove port mappings. Do not stop endpoint delete on unmap failure
|
||||
n.releasePorts(ep)
|
||||
|
||||
// Try removal of link. Discard error: link pair might have
|
||||
// already been deleted by sandbox delete. Make sure defer
|
||||
// does not see this error either.
|
||||
// Try removal of link. Discard error: it is a best effort.
|
||||
// Also make sure defer does not see this error either.
|
||||
if link, err := netlink.LinkByName(ep.srcName); err == nil {
|
||||
netlink.LinkDel(link)
|
||||
}
|
||||
|
|
|
@ -111,18 +111,35 @@ func TestCreateFullOptionsLabels(t *testing.T) {
|
|||
t.Fatalf("Failed to setup driver config: %v", err)
|
||||
}
|
||||
|
||||
bndIPs := "127.0.0.1"
|
||||
nwV6s := "2100:2400:2600:2700:2800::/80"
|
||||
gwV6s := "2100:2400:2600:2700:2800::25/80"
|
||||
nwV6, _ := types.ParseCIDR(nwV6s)
|
||||
gwV6, _ := types.ParseCIDR(gwV6s)
|
||||
|
||||
labels := map[string]string{
|
||||
BridgeName: "cu",
|
||||
BridgeName: DefaultBridgeName,
|
||||
DefaultBridge: "true",
|
||||
netlabel.EnableIPv6: "true",
|
||||
EnableICC: "true",
|
||||
EnableIPMasquerade: "true",
|
||||
DefaultBindingIP: "127.0.0.1",
|
||||
DefaultBindingIP: bndIPs,
|
||||
}
|
||||
|
||||
netOption := make(map[string]interface{})
|
||||
netOption[netlabel.GenericData] = labels
|
||||
|
||||
err := d.CreateNetwork("dummy", netOption, getIPv4Data(t), nil)
|
||||
ipdList := getIPv4Data(t)
|
||||
ipd6List := []driverapi.IPAMData{
|
||||
driverapi.IPAMData{
|
||||
Pool: nwV6,
|
||||
AuxAddresses: map[string]*net.IPNet{
|
||||
DefaultGatewayV6AuxKey: gwV6,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := d.CreateNetwork("dummy", netOption, ipdList, ipd6List)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create bridge: %v", err)
|
||||
}
|
||||
|
@ -132,7 +149,7 @@ func TestCreateFullOptionsLabels(t *testing.T) {
|
|||
t.Fatalf("Cannot find dummy network in bridge driver")
|
||||
}
|
||||
|
||||
if nw.config.BridgeName != "cu" {
|
||||
if nw.config.BridgeName != DefaultBridgeName {
|
||||
t.Fatalf("incongruent name in bridge network")
|
||||
}
|
||||
|
||||
|
@ -147,6 +164,36 @@ func TestCreateFullOptionsLabels(t *testing.T) {
|
|||
if !nw.config.EnableIPMasquerade {
|
||||
t.Fatalf("incongruent EnableIPMasquerade in bridge network")
|
||||
}
|
||||
|
||||
bndIP := net.ParseIP(bndIPs)
|
||||
if !bndIP.Equal(nw.config.DefaultBindingIP) {
|
||||
t.Fatalf("Unexpected: %v", nw.config.DefaultBindingIP)
|
||||
}
|
||||
|
||||
if !types.CompareIPNet(nw.config.AddressIPv6, nwV6) {
|
||||
t.Fatalf("Unexpected: %v", nw.config.AddressIPv6)
|
||||
}
|
||||
|
||||
if !gwV6.IP.Equal(nw.config.DefaultGatewayIPv6) {
|
||||
t.Fatalf("Unexpected: %v", nw.config.DefaultGatewayIPv6)
|
||||
}
|
||||
|
||||
// In short here we are testing --fixed-cidr-v6 daemon option
|
||||
// plus --mac-address run option
|
||||
mac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
|
||||
epOptions := map[string]interface{}{netlabel.MacAddress: mac}
|
||||
te := newTestEndpoint(ipdList[0].Pool, 20)
|
||||
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !nwV6.Contains(te.Interface().AddressIPv6().IP) {
|
||||
t.Fatalf("endpoint got assigned address outside of container network(%s): %s", nwV6.String(), te.Interface().AddressIPv6())
|
||||
}
|
||||
if te.Interface().AddressIPv6().IP.String() != "2100:2400:2600:2700:2800:aabb:ccdd:eeff" {
|
||||
t.Fatalf("Unexpected endpoint IPv6 address: %v", te.Interface().AddressIPv6().IP)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
package overlay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libnetwork/iptables"
|
||||
)
|
||||
|
||||
const globalChain = "DOCKER-OVERLAY"
|
||||
|
||||
var filterOnce sync.Once
|
||||
|
||||
func rawIPTables(args ...string) error {
|
||||
if output, err := iptables.Raw(args...); err != nil {
|
||||
return fmt.Errorf("unable to add overlay filter: %v", err)
|
||||
} else if len(output) != 0 {
|
||||
return fmt.Errorf("unable to add overlay filter: %s", string(output))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func chainExists(cname string) bool {
|
||||
if _, err := iptables.Raw("-L", cname); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func setupGlobalChain() {
|
||||
if err := rawIPTables("-N", globalChain); err != nil {
|
||||
logrus.Debugf("could not create global overlay chain: %v", err)
|
||||
}
|
||||
|
||||
if err := rawIPTables("-A", globalChain, "-j", "RETURN"); err != nil {
|
||||
logrus.Debugf("could not install default return chain in the overlay global chain: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setNetworkChain(cname string, remove bool) error {
|
||||
// Initialize the onetime global overlay chain
|
||||
filterOnce.Do(setupGlobalChain)
|
||||
|
||||
exists := chainExists(cname)
|
||||
|
||||
opt := "-N"
|
||||
// In case of remove, make sure to flush the rules in the chain
|
||||
if remove && exists {
|
||||
if err := rawIPTables("-F", cname); err != nil {
|
||||
return fmt.Errorf("failed to flush overlay network chain %s rules: %v", cname, err)
|
||||
}
|
||||
opt = "-X"
|
||||
}
|
||||
|
||||
if (!remove && !exists) || (remove && exists) {
|
||||
if err := rawIPTables(opt, cname); err != nil {
|
||||
return fmt.Errorf("failed network chain operation %q for chain %s: %v", opt, cname, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !remove {
|
||||
if !iptables.Exists(iptables.Filter, cname, "-j", "DROP") {
|
||||
if err := rawIPTables("-A", cname, "-j", "DROP"); err != nil {
|
||||
return fmt.Errorf("failed adding default drop rule to overlay network chain %s: %v", cname, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addNetworkChain(cname string) error {
|
||||
return setNetworkChain(cname, false)
|
||||
}
|
||||
|
||||
func removeNetworkChain(cname string) error {
|
||||
return setNetworkChain(cname, true)
|
||||
}
|
||||
|
||||
func setFilters(cname, brName string, remove bool) error {
|
||||
opt := "-I"
|
||||
if remove {
|
||||
opt = "-D"
|
||||
}
|
||||
|
||||
// Everytime we set filters for a new subnet make sure to move the global overlay hook to the top of the both the OUTPUT and forward chains
|
||||
if !remove {
|
||||
for _, chain := range []string{"OUTPUT", "FORWARD"} {
|
||||
exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain)
|
||||
if exists {
|
||||
if err := rawIPTables("-D", chain, "-j", globalChain); err != nil {
|
||||
return fmt.Errorf("failed to delete overlay hook in chain %s while moving the hook: %v", chain, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := rawIPTables("-I", chain, "-j", globalChain); err != nil {
|
||||
return fmt.Errorf("failed to insert overlay hook in chain %s: %v", chain, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert/Delete the rule to jump to per-bridge chain
|
||||
exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname)
|
||||
if (!remove && !exists) || (remove && exists) {
|
||||
if err := rawIPTables(opt, globalChain, "-o", brName, "-j", cname); err != nil {
|
||||
return fmt.Errorf("failed to add per-bridge filter rule for bridge %s, network chain %s: %v", brName, cname, err)
|
||||
}
|
||||
}
|
||||
|
||||
exists = iptables.Exists(iptables.Filter, cname, "-i", brName, "-j", "ACCEPT")
|
||||
if (!remove && exists) || (remove && !exists) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := rawIPTables(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil {
|
||||
return fmt.Errorf("failed to add overlay filter rile for network chain %s, bridge %s: %v", cname, brName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addFilters(cname, brName string) error {
|
||||
return setFilters(cname, brName, false)
|
||||
}
|
||||
|
||||
func removeFilters(cname, brName string) error {
|
||||
return setFilters(cname, brName, true)
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
|
@ -12,11 +13,17 @@ import (
|
|||
"github.com/docker/libnetwork/driverapi"
|
||||
"github.com/docker/libnetwork/netutils"
|
||||
"github.com/docker/libnetwork/osl"
|
||||
"github.com/docker/libnetwork/resolvconf"
|
||||
"github.com/docker/libnetwork/types"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
)
|
||||
|
||||
var (
|
||||
hostMode bool
|
||||
hostModeOnce sync.Once
|
||||
)
|
||||
|
||||
type networkTable map[string]*network
|
||||
|
||||
type subnet struct {
|
||||
|
@ -87,22 +94,6 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Dat
|
|||
return nil
|
||||
}
|
||||
|
||||
/* func (d *driver) createNetworkfromStore(nid string) (*network, error) {
|
||||
n := &network{
|
||||
id: nid,
|
||||
driver: d,
|
||||
endpoints: endpointTable{},
|
||||
once: &sync.Once{},
|
||||
subnets: []*subnet{},
|
||||
}
|
||||
|
||||
err := d.store.GetObject(datastore.Key(n.Key()...), n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get network %q from data store, %v", nid, err)
|
||||
}
|
||||
return n, nil
|
||||
}*/
|
||||
|
||||
func (d *driver) DeleteNetwork(nid string) error {
|
||||
if nid == "" {
|
||||
return fmt.Errorf("invalid network id")
|
||||
|
@ -171,24 +162,110 @@ func (n *network) destroySandbox() {
|
|||
}
|
||||
|
||||
for _, s := range n.subnets {
|
||||
if hostMode {
|
||||
if err := removeFilters(n.id[:12], s.brName); err != nil {
|
||||
logrus.Warnf("Could not remove overlay filters: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if s.vxlanName != "" {
|
||||
err := deleteVxlan(s.vxlanName)
|
||||
err := deleteInterface(s.vxlanName)
|
||||
if err != nil {
|
||||
logrus.Warnf("could not cleanup sandbox properly: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hostMode {
|
||||
if err := removeNetworkChain(n.id[:12]); err != nil {
|
||||
logrus.Warnf("could not remove network chain: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sbox.Destroy()
|
||||
n.setSandbox(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *network) initSubnetSandbox(s *subnet) error {
|
||||
// create a bridge and vxlan device for this subnet and move it to the sandbox
|
||||
brName, err := netutils.GenerateIfaceName("bridge", 7)
|
||||
if err != nil {
|
||||
return err
|
||||
func setHostMode() {
|
||||
if os.Getenv("_OVERLAY_HOST_MODE") != "" {
|
||||
hostMode = true
|
||||
return
|
||||
}
|
||||
|
||||
err := createVxlan("testvxlan", 1)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to create testvxlan interface: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer deleteInterface("testvxlan")
|
||||
|
||||
path := "/proc/self/ns/net"
|
||||
f, err := os.OpenFile(path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to open path %s for network namespace for setting host mode: %v", path, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
nsFD := f.Fd()
|
||||
|
||||
iface, err := netlink.LinkByName("testvxlan")
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to get link testvxlan: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If we are not able to move the vxlan interface to a namespace
|
||||
// then fallback to host mode
|
||||
if err := netlink.LinkSetNsFd(iface, int(nsFD)); err != nil {
|
||||
hostMode = true
|
||||
}
|
||||
}
|
||||
|
||||
func (n *network) generateVxlanName(s *subnet) string {
|
||||
return "vx-" + fmt.Sprintf("%06x", n.vxlanID(s)) + "-" + n.id[:5]
|
||||
}
|
||||
|
||||
func (n *network) generateBridgeName(s *subnet) string {
|
||||
return "ov-" + fmt.Sprintf("%06x", n.vxlanID(s)) + "-" + n.id[:5]
|
||||
}
|
||||
|
||||
func isOverlap(nw *net.IPNet) bool {
|
||||
var nameservers []string
|
||||
|
||||
if rc, err := resolvconf.Get(); err == nil {
|
||||
nameservers = resolvconf.GetNameserversAsCIDR(rc.Content)
|
||||
}
|
||||
|
||||
if err := netutils.CheckNameserverOverlaps(nameservers, nw); err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if err := netutils.CheckRouteOverlaps(nw); err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *network) initSubnetSandbox(s *subnet) error {
|
||||
brName := n.generateBridgeName(s)
|
||||
vxlanName := n.generateVxlanName(s)
|
||||
|
||||
if hostMode {
|
||||
// Try to delete stale bridge interface if it exists
|
||||
deleteInterface(brName)
|
||||
// Try to delete the vxlan interface by vni if already present
|
||||
deleteVxlanByVNI(n.vxlanID(s))
|
||||
|
||||
if isOverlap(s.subnetIP) {
|
||||
return fmt.Errorf("overlay subnet %s has conflicts in the host while running in host mode", s.subnetIP.String())
|
||||
}
|
||||
}
|
||||
|
||||
// create a bridge and vxlan device for this subnet and move it to the sandbox
|
||||
sbox := n.sandbox()
|
||||
|
||||
if err := sbox.AddInterface(brName, "br",
|
||||
|
@ -197,7 +274,7 @@ func (n *network) initSubnetSandbox(s *subnet) error {
|
|||
return fmt.Errorf("bridge creation in sandbox failed for subnet %q: %v", s.subnetIP.String(), err)
|
||||
}
|
||||
|
||||
vxlanName, err := createVxlan(n.vxlanID(s))
|
||||
err := createVxlan(vxlanName, n.vxlanID(s))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -207,6 +284,12 @@ func (n *network) initSubnetSandbox(s *subnet) error {
|
|||
return fmt.Errorf("vxlan interface creation failed for subnet %q: %v", s.subnetIP.String(), err)
|
||||
}
|
||||
|
||||
if hostMode {
|
||||
if err := addFilters(n.id[:12], brName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
n.Lock()
|
||||
s.vxlanName = vxlanName
|
||||
s.brName = brName
|
||||
|
@ -220,8 +303,16 @@ func (n *network) initSandbox() error {
|
|||
n.initEpoch++
|
||||
n.Unlock()
|
||||
|
||||
hostModeOnce.Do(setHostMode)
|
||||
|
||||
if hostMode {
|
||||
if err := addNetworkChain(n.id[:12]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sbox, err := osl.NewSandbox(
|
||||
osl.GenerateKey(fmt.Sprintf("%d-", n.initEpoch)+n.id), true)
|
||||
osl.GenerateKey(fmt.Sprintf("%d-", n.initEpoch)+n.id), !hostMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create network sandbox: %v", err)
|
||||
}
|
||||
|
|
|
@ -151,6 +151,10 @@ func (d *driver) processQuery(q *serf.Query) {
|
|||
}
|
||||
|
||||
func (d *driver) resolvePeer(nid string, peerIP net.IP) (net.HardwareAddr, net.IPMask, net.IP, error) {
|
||||
if d.serfInstance == nil {
|
||||
return nil, nil, nil, fmt.Errorf("could not resolve peer: serf instance not initialized")
|
||||
}
|
||||
|
||||
qPayload := fmt.Sprintf("%s %s", string(nid), peerIP.String())
|
||||
resp, err := d.serfInstance.Query("peerlookup", []byte(qPayload), nil)
|
||||
if err != nil {
|
||||
|
|
|
@ -47,14 +47,9 @@ func createVethPair() (string, string, error) {
|
|||
return name1, name2, nil
|
||||
}
|
||||
|
||||
func createVxlan(vni uint32) (string, error) {
|
||||
func createVxlan(name string, vni uint32) error {
|
||||
defer osl.InitOSContext()()
|
||||
|
||||
name, err := netutils.GenerateIfaceName("vxlan", 7)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error generating vxlan name: %v", err)
|
||||
}
|
||||
|
||||
vxlan := &netlink.Vxlan{
|
||||
LinkAttrs: netlink.LinkAttrs{Name: name},
|
||||
VxlanId: int(vni),
|
||||
|
@ -66,23 +61,45 @@ func createVxlan(vni uint32) (string, error) {
|
|||
}
|
||||
|
||||
if err := netlink.LinkAdd(vxlan); err != nil {
|
||||
return "", fmt.Errorf("error creating vxlan interface: %v", err)
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func deleteVxlan(name string) error {
|
||||
defer osl.InitOSContext()()
|
||||
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find vxlan interface with name %s: %v", name, err)
|
||||
}
|
||||
|
||||
if err := netlink.LinkDel(link); err != nil {
|
||||
return fmt.Errorf("error deleting vxlan interface: %v", err)
|
||||
return fmt.Errorf("error creating vxlan interface: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteInterface(name string) error {
|
||||
defer osl.InitOSContext()()
|
||||
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find interface with name %s: %v", name, err)
|
||||
}
|
||||
|
||||
if err := netlink.LinkDel(link); err != nil {
|
||||
return fmt.Errorf("error deleting interface with name %s: %v", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteVxlanByVNI(vni uint32) error {
|
||||
defer osl.InitOSContext()()
|
||||
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list interfaces while deleting vxlan interface by vni: %v", err)
|
||||
}
|
||||
|
||||
for _, l := range links {
|
||||
if l.Type() == "vxlan" && l.(*netlink.Vxlan).VxlanId == int(vni) {
|
||||
err = netlink.LinkDel(l)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting vxlan interface with id %d: %v", vni, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("could not find a vxlan interface to delete with id %d", vni)
|
||||
}
|
||||
|
|
34
endpoint.go
34
endpoint.go
|
@ -386,6 +386,9 @@ func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error {
|
|||
}
|
||||
}()
|
||||
|
||||
// Watch for service records
|
||||
network.getController().watchSvcRecord(ep)
|
||||
|
||||
address := ""
|
||||
if ip := ep.getFirstInterfaceAddress(); ip != nil {
|
||||
address = ip.String()
|
||||
|
@ -394,9 +397,6 @@ func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Watch for service records
|
||||
network.getController().watchSvcRecord(ep)
|
||||
|
||||
if err = sb.updateDNS(network.enableIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -559,7 +559,7 @@ func (ep *endpoint) sbLeave(sbox Sandbox, options ...EndpointOption) error {
|
|||
|
||||
sb.deleteHostsEntries(n.getSvcRecords(ep))
|
||||
|
||||
if sb.needDefaultGW() {
|
||||
if !sb.inDelete && sb.needDefaultGW() {
|
||||
ep := sb.getEPwithoutGateway()
|
||||
if ep == nil {
|
||||
return fmt.Errorf("endpoint without GW expected, but not found")
|
||||
|
@ -584,7 +584,8 @@ func (ep *endpoint) Delete() error {
|
|||
ep.Lock()
|
||||
epid := ep.id
|
||||
name := ep.name
|
||||
if ep.sandboxID != "" {
|
||||
sb, _ := n.getController().SandboxByID(ep.sandboxID)
|
||||
if sb != nil {
|
||||
ep.Unlock()
|
||||
return &ActiveContainerError{name: name, id: epid}
|
||||
}
|
||||
|
@ -737,7 +738,7 @@ func (ep *endpoint) DataScope() string {
|
|||
return ep.getNetwork().DataScope()
|
||||
}
|
||||
|
||||
func (ep *endpoint) assignAddress() error {
|
||||
func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
|
||||
var (
|
||||
ipam ipamapi.Ipam
|
||||
err error
|
||||
|
@ -754,11 +755,18 @@ func (ep *endpoint) assignAddress() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ep.assignAddressVersion(4, ipam)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
if assignIPv4 {
|
||||
if err = ep.assignAddressVersion(4, ipam); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ep.assignAddressVersion(6, ipam)
|
||||
|
||||
if assignIPv6 {
|
||||
err = ep.assignAddressVersion(6, ipam)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
|
||||
|
@ -787,7 +795,11 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
|
|||
}
|
||||
|
||||
for _, d := range ipInfo {
|
||||
addr, _, err := ipam.RequestAddress(d.PoolID, nil, nil)
|
||||
var prefIP net.IP
|
||||
if *address != nil {
|
||||
prefIP = (*address).IP
|
||||
}
|
||||
addr, _, err := ipam.RequestAddress(d.PoolID, prefIP, nil)
|
||||
if err == nil {
|
||||
ep.Lock()
|
||||
*address = addr
|
||||
|
|
|
@ -159,7 +159,11 @@ func (ep *endpoint) Info() EndpointInfo {
|
|||
return ep
|
||||
}
|
||||
|
||||
return sb.getEndpoint(ep.ID())
|
||||
if epi := sb.getEndpoint(ep.ID()); epi != nil {
|
||||
return epi
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *endpoint) DriverInfo() (map[string]interface{}, error) {
|
||||
|
|
|
@ -220,7 +220,7 @@ func (a *Allocator) parsePoolRequest(addressSpace, pool, subPool string, v6 bool
|
|||
return nil, nil, nil, ipamapi.ErrInvalidPool
|
||||
}
|
||||
if subPool != "" {
|
||||
if ipr, err = getAddressRange(subPool); err != nil {
|
||||
if ipr, err = getAddressRange(subPool, nw); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ func (a *Allocator) getPredefinedPool(as string, ipV6 bool) (*net.IPNet, error)
|
|||
}
|
||||
|
||||
if as != localAddressSpace && as != globalAddressSpace {
|
||||
return nil, fmt.Errorf("no default pool availbale for non-default addresss spaces")
|
||||
return nil, fmt.Errorf("no default pool available for non-default address spaces")
|
||||
}
|
||||
|
||||
aSpace, err := a.getAddrSpace(as)
|
||||
|
@ -431,9 +431,6 @@ func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
|
|||
aSpace.Unlock()
|
||||
|
||||
mask := p.Pool.Mask
|
||||
if p.Range != nil {
|
||||
mask = p.Range.Sub.Mask
|
||||
}
|
||||
|
||||
h, err := types.GetHostPartIP(address, mask)
|
||||
if err != nil {
|
||||
|
@ -471,7 +468,6 @@ func (a *Allocator) getAddress(nw *net.IPNet, bitmask *bitseq.Handle, prefAddres
|
|||
ordinal = ipToUint64(types.GetMinimalIP(hostPart))
|
||||
err = bitmask.Set(ordinal)
|
||||
} else {
|
||||
base.IP = ipr.Sub.IP
|
||||
ordinal, err = bitmask.SetAnyInRange(ipr.Start, ipr.End)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
|
@ -666,6 +666,59 @@ func TestRequestReleaseAddressFromSubPool(t *testing.T) {
|
|||
if !types.CompareIPNet(rp, ip) {
|
||||
t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip)
|
||||
}
|
||||
|
||||
// Request any addresses from subpool after explicit address request
|
||||
unoExp, _ := types.ParseCIDR("10.2.2.0/16")
|
||||
dueExp, _ := types.ParseCIDR("10.2.2.2/16")
|
||||
treExp, _ := types.ParseCIDR("10.2.2.1/16")
|
||||
if poolID, _, _, err = a.RequestPool("rosso", "10.2.0.0/16", "10.2.2.0/24", nil, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tre, _, err := a.RequestAddress(poolID, treExp.IP, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !types.CompareIPNet(tre, treExp) {
|
||||
t.Fatalf("Unexpected address: %v", tre)
|
||||
}
|
||||
|
||||
uno, _, err := a.RequestAddress(poolID, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !types.CompareIPNet(uno, unoExp) {
|
||||
t.Fatalf("Unexpected address: %v", uno)
|
||||
}
|
||||
|
||||
due, _, err := a.RequestAddress(poolID, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !types.CompareIPNet(due, dueExp) {
|
||||
t.Fatalf("Unexpected address: %v", due)
|
||||
}
|
||||
|
||||
if err = a.ReleaseAddress(poolID, uno.IP); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
uno, _, err = a.RequestAddress(poolID, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !types.CompareIPNet(uno, unoExp) {
|
||||
t.Fatalf("Unexpected address: %v", uno)
|
||||
}
|
||||
|
||||
if err = a.ReleaseAddress(poolID, tre.IP); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tre, _, err = a.RequestAddress(poolID, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !types.CompareIPNet(tre, treExp) {
|
||||
t.Fatalf("Unexpected address: %v", tre)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAddress(t *testing.T) {
|
||||
|
|
|
@ -15,12 +15,12 @@ const (
|
|||
v6 = 6
|
||||
)
|
||||
|
||||
func getAddressRange(pool string) (*AddressRange, error) {
|
||||
func getAddressRange(pool string, masterNw *net.IPNet) (*AddressRange, error) {
|
||||
ip, nw, err := net.ParseCIDR(pool)
|
||||
if err != nil {
|
||||
return nil, ipamapi.ErrInvalidSubPool
|
||||
}
|
||||
lIP, e := types.GetHostPartIP(nw.IP, nw.Mask)
|
||||
lIP, e := types.GetHostPartIP(nw.IP, masterNw.Mask)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's lowest ip address: %v", e)
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ func getAddressRange(pool string) (*AddressRange, error) {
|
|||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's broadcast ip address: %v", e)
|
||||
}
|
||||
hIP, e := types.GetHostPartIP(bIP, nw.Mask)
|
||||
hIP, e := types.GetHostPartIP(bIP, masterNw.Mask)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to compute range's highest ip address: %v", e)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
|
@ -313,6 +314,59 @@ func TestBridge(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Testing IPV6 from MAC address
|
||||
func TestBridgeIpv6FromMac(t *testing.T) {
|
||||
if !testutils.IsRunningInContainer() {
|
||||
defer testutils.SetupTestOSContext(t)()
|
||||
}
|
||||
|
||||
netOption := options.Generic{
|
||||
netlabel.GenericData: options.Generic{
|
||||
"BridgeName": "testipv6mac",
|
||||
"EnableIPv6": true,
|
||||
"EnableICC": true,
|
||||
"EnableIPMasquerade": true,
|
||||
},
|
||||
}
|
||||
ipamV4ConfList := []*libnetwork.IpamConf{&libnetwork.IpamConf{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}}
|
||||
ipamV6ConfList := []*libnetwork.IpamConf{&libnetwork.IpamConf{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
|
||||
|
||||
network, err := controller.NewNetwork(bridgeNetType, "testipv6mac",
|
||||
libnetwork.NetworkOptionGeneric(netOption),
|
||||
libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4ConfList, ipamV6ConfList),
|
||||
libnetwork.NetworkOptionDeferIPv6Alloc(true))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mac := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
|
||||
epOption := options.Generic{netlabel.MacAddress: mac}
|
||||
|
||||
ep, err := network.CreateEndpoint("testep", libnetwork.EndpointOptionGeneric(epOption))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
iface := ep.Info().Iface()
|
||||
if !bytes.Equal(iface.MacAddress(), mac) {
|
||||
t.Fatalf("Unexpected mac address: %v", iface.MacAddress())
|
||||
}
|
||||
|
||||
ip, expIP, _ := net.ParseCIDR("fe90::aabb:ccdd:eeff/64")
|
||||
expIP.IP = ip
|
||||
if !types.CompareIPNet(expIP, iface.AddressIPv6()) {
|
||||
t.Fatalf("Expected %v. Got: %v", expIP, iface.AddressIPv6())
|
||||
}
|
||||
|
||||
if err := ep.Delete(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := network.Delete(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownDriver(t *testing.T) {
|
||||
if !testutils.IsRunningInContainer() {
|
||||
defer testutils.SetupTestOSContext(t)()
|
||||
|
|
33
network.go
33
network.go
|
@ -152,6 +152,7 @@ type network struct {
|
|||
ipamV4Info []*IpamInfo
|
||||
ipamV6Info []*IpamInfo
|
||||
enableIPv6 bool
|
||||
postIPv6 bool
|
||||
epCnt *endpointCnt
|
||||
generic options.Generic
|
||||
dbIndex uint64
|
||||
|
@ -298,6 +299,7 @@ func (n *network) CopyTo(o datastore.KVObject) error {
|
|||
dstN.ipamType = n.ipamType
|
||||
dstN.enableIPv6 = n.enableIPv6
|
||||
dstN.persist = n.persist
|
||||
dstN.postIPv6 = n.postIPv6
|
||||
dstN.dbIndex = n.dbIndex
|
||||
dstN.dbExists = n.dbExists
|
||||
dstN.drvOnce = n.drvOnce
|
||||
|
@ -358,6 +360,7 @@ func (n *network) MarshalJSON() ([]byte, error) {
|
|||
netMap["generic"] = n.generic
|
||||
}
|
||||
netMap["persist"] = n.persist
|
||||
netMap["postIPv6"] = n.postIPv6
|
||||
if len(n.ipamV4Config) > 0 {
|
||||
ics, err := json.Marshal(n.ipamV4Config)
|
||||
if err != nil {
|
||||
|
@ -418,6 +421,9 @@ func (n *network) UnmarshalJSON(b []byte) (err error) {
|
|||
if v, ok := netMap["persist"]; ok {
|
||||
n.persist = v.(bool)
|
||||
}
|
||||
if v, ok := netMap["postIPv6"]; ok {
|
||||
n.postIPv6 = v.(bool)
|
||||
}
|
||||
if v, ok := netMap["ipamType"]; ok {
|
||||
n.ipamType = v.(string)
|
||||
} else {
|
||||
|
@ -505,6 +511,16 @@ func NetworkOptionDriverOpts(opts map[string]string) NetworkOption {
|
|||
}
|
||||
}
|
||||
|
||||
// NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created
|
||||
// It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address
|
||||
// to a container as combination of fixed-cidr-v6 + mac-address
|
||||
// TODO: Remove this option setter once we support endpoint ipam options
|
||||
func NetworkOptionDeferIPv6Alloc(enable bool) NetworkOption {
|
||||
return func(n *network) {
|
||||
n.postIPv6 = enable
|
||||
}
|
||||
}
|
||||
|
||||
func (n *network) processOptions(options ...NetworkOption) {
|
||||
for _, opt := range options {
|
||||
if opt != nil {
|
||||
|
@ -587,12 +603,13 @@ func (n *network) Delete() error {
|
|||
if err = n.getController().deleteFromStore(n.getEpCnt()); err != nil {
|
||||
return fmt.Errorf("error deleting network endpoint count from store: %v", err)
|
||||
}
|
||||
|
||||
n.ipamRelease()
|
||||
|
||||
if err = n.getController().deleteFromStore(n); err != nil {
|
||||
return fmt.Errorf("error deleting network from store: %v", err)
|
||||
}
|
||||
|
||||
n.ipamRelease()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -655,7 +672,13 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
|
|||
|
||||
ep.processOptions(options...)
|
||||
|
||||
if err = ep.assignAddress(); err != nil {
|
||||
if opt, ok := ep.generic[netlabel.MacAddress]; ok {
|
||||
if mac, ok := opt.(net.HardwareAddr); ok {
|
||||
ep.iface.mac = mac
|
||||
}
|
||||
}
|
||||
|
||||
if err = ep.assignAddress(true, !n.postIPv6); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
|
@ -675,6 +698,10 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
|
|||
}
|
||||
}()
|
||||
|
||||
if err = ep.assignAddress(false, n.postIPv6); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = n.getController().updateToStore(ep); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ func (i *nwIface) Remove() error {
|
|||
|
||||
n.Lock()
|
||||
path := n.path
|
||||
isDefault := n.isDefault
|
||||
n.Unlock()
|
||||
|
||||
return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
|
||||
|
@ -134,7 +135,7 @@ func (i *nwIface) Remove() error {
|
|||
if err := netlink.LinkDel(iface); err != nil {
|
||||
return fmt.Errorf("failed deleting bridge %q: %v", i.SrcName(), err)
|
||||
}
|
||||
} else {
|
||||
} else if !isDefault {
|
||||
// Move the network interface to caller namespace.
|
||||
if err := netlink.LinkSetNsFd(iface, callerFD); err != nil {
|
||||
fmt.Println("LinkSetNsPid failed: ", err)
|
||||
|
@ -213,9 +214,15 @@ func (n *networkNamespace) AddInterface(srcName, dstPrefix string, options ...If
|
|||
}
|
||||
|
||||
n.Lock()
|
||||
i.dstName = fmt.Sprintf("%s%d", i.dstName, n.nextIfIndex)
|
||||
n.nextIfIndex++
|
||||
if n.isDefault {
|
||||
i.dstName = i.srcName
|
||||
} else {
|
||||
i.dstName = fmt.Sprintf("%s%d", i.dstName, n.nextIfIndex)
|
||||
n.nextIfIndex++
|
||||
}
|
||||
|
||||
path := n.path
|
||||
isDefault := n.isDefault
|
||||
n.Unlock()
|
||||
|
||||
return nsInvoke(path, func(nsFD int) error {
|
||||
|
@ -231,9 +238,13 @@ func (n *networkNamespace) AddInterface(srcName, dstPrefix string, options ...If
|
|||
return fmt.Errorf("failed to get link by name %q: %v", i.srcName, err)
|
||||
}
|
||||
|
||||
// Move the network interface to the destination namespace.
|
||||
if err := netlink.LinkSetNsFd(iface, nsFD); err != nil {
|
||||
return fmt.Errorf("failed to set namespace on link %q: %v", i.srcName, err)
|
||||
// Move the network interface to the destination
|
||||
// namespace only if the namespace is not a default
|
||||
// type
|
||||
if !isDefault {
|
||||
if err := netlink.LinkSetNsFd(iface, nsFD); err != nil {
|
||||
return fmt.Errorf("failed to set namespace on link %q: %v", i.srcName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -41,6 +41,7 @@ type networkNamespace struct {
|
|||
staticRoutes []*types.StaticRoute
|
||||
neighbors []*neigh
|
||||
nextIfIndex int
|
||||
isDefault bool
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
|
@ -146,7 +147,7 @@ func NewSandbox(key string, osCreate bool) (Sandbox, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &networkNamespace{path: key}, nil
|
||||
return &networkNamespace{path: key, isDefault: !osCreate}, nil
|
||||
}
|
||||
|
||||
func (n *networkNamespace) InterfaceOptions() IfaceOptionSetter {
|
||||
|
|
71
sandbox.go
71
sandbox.go
|
@ -62,7 +62,6 @@ type sandbox struct {
|
|||
osSbox osl.Sandbox
|
||||
controller *controller
|
||||
refCnt int
|
||||
hostsOnce sync.Once
|
||||
endpoints epHeap
|
||||
epPriority map[string]int
|
||||
joinLeaveDone chan struct{}
|
||||
|
@ -177,13 +176,18 @@ func (sb *sandbox) Delete() error {
|
|||
continue
|
||||
}
|
||||
|
||||
if err := ep.Leave(sb); err != nil {
|
||||
// Retain the sanbdox if we can't obtain the network from store.
|
||||
if _, err := c.getNetworkFromStore(ep.getNetwork().ID()); err != nil {
|
||||
retain = true
|
||||
log.Warnf("Failed getting network for ep %s during sandbox %s delete: %v", ep.ID(), sb.ID(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := ep.Leave(sb); err != nil {
|
||||
log.Warnf("Failed detaching sandbox %s from endpoint %s: %v\n", sb.ID(), ep.ID(), err)
|
||||
}
|
||||
|
||||
if err := ep.Delete(); err != nil {
|
||||
retain = true
|
||||
log.Warnf("Failed deleting endpoint %s: %v\n", ep.ID(), err)
|
||||
}
|
||||
}
|
||||
|
@ -455,7 +459,7 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error {
|
|||
i := ep.iface
|
||||
ep.Unlock()
|
||||
|
||||
if i.srcName != "" {
|
||||
if i != nil && i.srcName != "" {
|
||||
var ifaceOptions []osl.IfaceOption
|
||||
|
||||
ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes))
|
||||
|
@ -596,41 +600,21 @@ func (sb *sandbox) buildHostsFile() error {
|
|||
}
|
||||
|
||||
func (sb *sandbox) updateHostsFile(ifaceIP string, svcRecords []etchosts.Record) error {
|
||||
var err error
|
||||
var mhost string
|
||||
|
||||
if sb.config.originHostsPath != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
max := func(a, b int) int {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
|
||||
return a
|
||||
if sb.config.domainName != "" {
|
||||
mhost = fmt.Sprintf("%s.%s %s", sb.config.hostName, sb.config.domainName,
|
||||
sb.config.hostName)
|
||||
} else {
|
||||
mhost = sb.config.hostName
|
||||
}
|
||||
|
||||
extraContent := make([]etchosts.Record, 0,
|
||||
max(len(sb.config.extraHosts), len(svcRecords)))
|
||||
|
||||
sb.hostsOnce.Do(func() {
|
||||
// Rebuild the hosts file accounting for the passed
|
||||
// interface IP and service records
|
||||
|
||||
for _, extraHost := range sb.config.extraHosts {
|
||||
extraContent = append(extraContent,
|
||||
etchosts.Record{Hosts: extraHost.name, IP: extraHost.IP})
|
||||
}
|
||||
|
||||
err = etchosts.Build(sb.config.hostsPath, ifaceIP,
|
||||
sb.config.hostName, sb.config.domainName, extraContent)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
extraContent = extraContent[:0]
|
||||
extraContent := make([]etchosts.Record, 0, len(svcRecords)+1)
|
||||
extraContent = append(extraContent, etchosts.Record{Hosts: mhost, IP: ifaceIP})
|
||||
for _, svc := range svcRecords {
|
||||
extraContent = append(extraContent, svc)
|
||||
}
|
||||
|
@ -951,6 +935,11 @@ func OptionGeneric(generic map[string]interface{}) SandboxOption {
|
|||
func (eh epHeap) Len() int { return len(eh) }
|
||||
|
||||
func (eh epHeap) Less(i, j int) bool {
|
||||
var (
|
||||
cip, cjp int
|
||||
ok bool
|
||||
)
|
||||
|
||||
ci, _ := eh[i].getSandbox()
|
||||
cj, _ := eh[j].getSandbox()
|
||||
|
||||
|
@ -965,14 +954,20 @@ func (eh epHeap) Less(i, j int) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
cip, ok := ci.epPriority[eh[i].ID()]
|
||||
if !ok {
|
||||
cip = 0
|
||||
if ci != nil {
|
||||
cip, ok = ci.epPriority[eh[i].ID()]
|
||||
if !ok {
|
||||
cip = 0
|
||||
}
|
||||
}
|
||||
cjp, ok := cj.epPriority[eh[j].ID()]
|
||||
if !ok {
|
||||
cjp = 0
|
||||
|
||||
if cj != nil {
|
||||
cjp, ok = cj.epPriority[eh[j].ID()]
|
||||
if !ok {
|
||||
cjp = 0
|
||||
}
|
||||
}
|
||||
|
||||
if cip == cjp {
|
||||
return eh[i].network.Name() < eh[j].network.Name()
|
||||
}
|
||||
|
|
1
store.go
1
store.go
|
@ -176,7 +176,6 @@ func (n *network) getEndpointsFromStore() ([]*endpoint, error) {
|
|||
|
||||
for _, kvo := range kvol {
|
||||
ep := kvo.(*endpoint)
|
||||
ep.network = n
|
||||
epl = append(epl, ep)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,6 +163,7 @@ EOF
|
|||
--name=${name} \
|
||||
--privileged \
|
||||
-p ${hport}:${cport} \
|
||||
-e _OVERLAY_HOST_MODE \
|
||||
-v $(pwd)/:/go/src/github.com/docker/libnetwork \
|
||||
-v /tmp:/tmp \
|
||||
-v $(pwd)/${TMPC_ROOT}:/scratch \
|
||||
|
@ -215,6 +216,21 @@ function runc() {
|
|||
dnet_exec ${dnet} "umount /var/run/netns/c && rm /var/run/netns/c"
|
||||
}
|
||||
|
||||
function runc_nofail() {
|
||||
local dnet
|
||||
|
||||
dnet=${1}
|
||||
shift
|
||||
dnet_exec ${dnet} "cp /var/lib/docker/network/files/${1}*/* /scratch/rootfs/etc"
|
||||
dnet_exec ${dnet} "mkdir -p /var/run/netns"
|
||||
dnet_exec ${dnet} "touch /var/run/netns/c && mount -o bind /var/run/docker/netns/${1} /var/run/netns/c"
|
||||
set +e
|
||||
dnet_exec ${dnet} "ip netns exec c unshare -fmuip --mount-proc chroot \"/scratch/rootfs\" /bin/sh -c \"/bin/mount -t proc proc /proc && ${2}\""
|
||||
status="$?"
|
||||
set -e
|
||||
dnet_exec ${dnet} "umount /var/run/netns/c && rm /var/run/netns/c"
|
||||
}
|
||||
|
||||
function start_etcd() {
|
||||
local bridge_ip
|
||||
stop_etcd
|
||||
|
@ -442,3 +458,83 @@ function test_overlay_singlehost() {
|
|||
|
||||
dnet_cmd $(inst_id2port 1) network rm multihost
|
||||
}
|
||||
|
||||
function test_overlay_hostmode() {
|
||||
dnet_suffix=$1
|
||||
shift
|
||||
|
||||
echo $(docker ps)
|
||||
|
||||
start=1
|
||||
end=2
|
||||
# Setup overlay network and connect containers ot it
|
||||
dnet_cmd $(inst_id2port 1) network create -d overlay multihost1
|
||||
dnet_cmd $(inst_id2port 1) network create -d overlay multihost2
|
||||
dnet_cmd $(inst_id2port 1) network ls
|
||||
|
||||
for i in `seq ${start} ${end}`;
|
||||
do
|
||||
dnet_cmd $(inst_id2port 1) container create mh1_${i}
|
||||
net_connect 1 mh1_${i} multihost1
|
||||
done
|
||||
|
||||
for i in `seq ${start} ${end}`;
|
||||
do
|
||||
dnet_cmd $(inst_id2port 1) container create mh2_${i}
|
||||
net_connect 1 mh2_${i} multihost2
|
||||
done
|
||||
|
||||
# Now test connectivity between all the containers using service names
|
||||
for i in `seq ${start} ${end}`;
|
||||
do
|
||||
for j in `seq ${start} ${end}`;
|
||||
do
|
||||
if [ "$i" -eq "$j" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Find the IP addresses of the j containers on both networks
|
||||
hrun runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh1_${i}) "nslookup mh1_$j"
|
||||
mh1_j_ip=$(echo ${output} | awk '{print $11}')
|
||||
|
||||
hrun runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh2_${i}) "nslookup mh2_$j"
|
||||
mh2_j_ip=$(echo ${output} | awk '{print $11}')
|
||||
|
||||
# Ping the j containers in the same network and ensure they are successfull
|
||||
runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh1_${i}) \
|
||||
"ping -c 1 mh1_$j"
|
||||
runc $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh2_${i}) \
|
||||
"ping -c 1 mh2_$j"
|
||||
|
||||
# Try pinging j container IPs from the container in the other network and make sure that they are not successfull
|
||||
runc_nofail $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh1_${i}) "ping -c 1 ${mh2_j_ip}"
|
||||
[ "${status}" -ne 0 ]
|
||||
|
||||
runc_nofail $(dnet_container_name 1 $dnet_suffix) $(get_sbox_id 1 mh2_${i}) "ping -c 1 ${mh1_j_ip}"
|
||||
[ "${status}" -ne 0 ]
|
||||
|
||||
# Try pinging the j container IPS from the host(dnet container in this case) and make syre that they are not successfull
|
||||
hrun docker exec -it $(dnet_container_name 1 $dnet_suffix) "ping -c 1 ${mh1_j_ip}"
|
||||
[ "${status}" -ne 0 ]
|
||||
|
||||
hrun docker exec -it $(dnet_container_name 1 $dnet_suffix) "ping -c 1 ${mh2_j_ip}"
|
||||
[ "${status}" -ne 0 ]
|
||||
done
|
||||
done
|
||||
|
||||
# Teardown the container connections and the network
|
||||
for i in `seq ${start} ${end}`;
|
||||
do
|
||||
net_disconnect 1 mh1_${i} multihost1
|
||||
dnet_cmd $(inst_id2port 1) container rm mh1_${i}
|
||||
done
|
||||
|
||||
for i in `seq ${start} ${end}`;
|
||||
do
|
||||
net_disconnect 1 mh2_${i} multihost2
|
||||
dnet_cmd $(inst_id2port 1) container rm mh2_${i}
|
||||
done
|
||||
|
||||
dnet_cmd $(inst_id2port 1) network rm multihost1
|
||||
dnet_cmd $(inst_id2port 1) network rm multihost2
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ function is_network_exist() {
|
|||
|
||||
for j in `seq 1 3`;
|
||||
do
|
||||
run dnet_cmd $(inst_id2port 2) service unpublish ${osvc}.${oname}
|
||||
run dnet_cmd $(inst_id2port $i) service unpublish ${osvc}.${oname}
|
||||
echo ${output}
|
||||
[ "$status" -ne 0 ]
|
||||
run dnet_cmd $(inst_id2port $j) network rm ${oname}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# -*- mode: sh -*-
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load helpers
|
||||
|
||||
@test "Test overlay network hostmode with consul" {
|
||||
skip_for_circleci
|
||||
test_overlay_hostmode consul
|
||||
}
|
|
@ -56,6 +56,21 @@ function run_overlay_consul_tests() {
|
|||
unset cmap[dnet-3-consul]
|
||||
}
|
||||
|
||||
function run_overlay_consul_host_tests() {
|
||||
export _OVERLAY_HOST_MODE="true"
|
||||
## Setup
|
||||
start_dnet 1 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
|
||||
cmap[dnet-1-consul]=dnet-1-consul
|
||||
|
||||
## Run the test cases
|
||||
./integration-tmp/bin/bats ./test/integration/dnet/overlay-consul-host.bats
|
||||
|
||||
## Teardown
|
||||
stop_dnet 1 consul 1>>${INTEGRATION_ROOT}/test.log 2>&1
|
||||
unset cmap[dnet-1-consul]
|
||||
unset _OVERLAY_HOST_MODE
|
||||
}
|
||||
|
||||
function run_overlay_zk_tests() {
|
||||
## Test overlay network with zookeeper
|
||||
start_dnet 1 zookeeper 1>>${INTEGRATION_ROOT}/test.log 2>&1
|
||||
|
@ -207,7 +222,7 @@ if [ -z "$SUITES" ]; then
|
|||
# old kernel and limited docker environment.
|
||||
suites="dnet simple_consul multi_consul multi_zk multi_etcd"
|
||||
else
|
||||
suites="dnet simple_consul multi_consul multi_zk multi_etcd bridge overlay_consul overlay_zk overlay_etcd"
|
||||
suites="dnet simple_consul multi_consul multi_zk multi_etcd bridge overlay_consul overlay_consul_host overlay_zk overlay_etcd"
|
||||
fi
|
||||
else
|
||||
suites="$SUITES"
|
||||
|
|
Loading…
Reference in New Issue