Compare commits

...

25 Commits

Author SHA1 Message Date
aboch c6a0ee8aaf Merge pull request #836 from mrjana/rel
Cherry pick overlay fixes
2015-12-22 16:56:01 -08:00
Jana Radhakrishnan 448706c9d4 Don't treat non-nil output as error in ChainExists
ChainExists should not treat non-nil output as
error because there is always going to be some
output while dumping iptable rules.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
2015-12-22 16:31:56 -08:00
Jana Radhakrishnan b176a99c56 Check existence of network chain before creating
We check for existence of all filter rules in
overlay driver before creating it. We should
also do this for chain creation, because even though
we cleanup network chains when the last container
stops, there is a possibility of a stale network
chain in case of ungraceful restart.

Also cleaned up stale bridges if any exist due to
ungraceful shutdown of daemon.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
2015-12-22 16:31:44 -08:00
Jana Radhakrishnan c062be3842 Cleanup vxlan interface by id before creating
Currently we are cleaning up vxlan interfaces by name
before trying to setup an interface with the same name.
But this doesn't work for properly cleaning up vxlan
interfaces with the same vni, if the interface has a
a different name than the one expected. The fix is to
delete the interface based on vni.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
2015-12-22 16:31:26 -08:00
Jana Radhakrishnan 07880789c9 Merge pull request #827 from aboch/release-0.5-lcl
Cherry-picking a few fixes for 1.9.2
2015-12-18 13:54:25 -08:00
Alessandro Boch 5a193ee895 Skip defaultGw check if sandbox is being deleted
- On Sandbox deletion, during Leave of each
  connected endpoint, avoid the default gw
  check, which may create an unnecessary
  connection to the default gateway network.

Signed-off-by: Alessandro Boch <aboch@docker.com>
2015-12-18 13:15:28 -08:00
Alessandro Boch c6584c6fbd Release address pool before removing the network from store
- On network delete it is better to release the gateway address
  and address pool before removing the network from the datastore,
  given ipam data is dependent on network data.

Signed-off-by: Alessandro Boch <aboch@docker.com>
2015-12-18 13:14:40 -08:00
Alexander Morozov c87febef71 bitseq: fix race between CopyTo and set
Race detector message:

WARNING: DATA RACE
Write by goroutine 269:
  github.com/docker/libnetwork/bitseq.(*Handle).CopyTo()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/bitseq/store.go:85 +0x2f6
  github.com/docker/libnetwork/datastore.(*cache).get()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/datastore/cache.go:135 +0x307
  github.com/docker/libnetwork/datastore.(*datastore).GetObject()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/datastore/datastore.go:438 +0x121
  github.com/docker/libnetwork/bitseq.(*Handle).set()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/bitseq/sequence.go:254 +0x1a5
  github.com/docker/libnetwork/bitseq.(*Handle).Unset()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/bitseq/sequence.go:227 +0xb0
  github.com/docker/libnetwork/ipam.(*Allocator).ReleaseAddress()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/ipam/allocator.go:446 +0x10bc
  github.com/docker/libnetwork.(*endpoint).releaseAddress()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/endpoint.go:830 +0x731
  github.com/docker/libnetwork.(*endpoint).Delete()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/endpoint.go:624 +0x8d8
  github.com/docker/libnetwork.(*sandbox).Delete()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/sandbox.go:191 +0x1047
  github.com/docker/docker/daemon.(*Daemon).releaseNetwork()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/container_unix.go:1180 +0x676
  github.com/docker/docker/daemon.(*Daemon).Cleanup()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/start.go:157 +0x5d
  github.com/docker/docker/daemon.(*containerMonitor).Close()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/monitor.go:111 +0xa4
  github.com/docker/docker/daemon.(*containerMonitor).Start.func1()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/monitor.go:142 +0x14b
  github.com/docker/docker/daemon.(*containerMonitor).Start()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/monitor.go:223 +0x1159
  github.com/docker/docker/daemon.(*containerMonitor).Start-fm()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/start.go:147 +0x3b
  github.com/docker/docker/pkg/promise.Go.func1()
      /home/moroz/project/workspace/src/github.com/docker/docker/pkg/promise/promise.go:8 +0x2a

Previous read by goroutine 340:
  github.com/docker/libnetwork/bitseq.(*Handle).set()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/bitseq/sequence.go:254 +0x133
  github.com/docker/libnetwork/bitseq.(*Handle).Unset()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/bitseq/sequence.go:227 +0xb0
  github.com/docker/libnetwork/ipam.(*Allocator).ReleaseAddress()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/ipam/allocator.go:446 +0x10bc
  github.com/docker/libnetwork.(*endpoint).releaseAddress()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/endpoint.go:830 +0x731
  github.com/docker/libnetwork.(*endpoint).Delete()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/endpoint.go:624 +0x8d8
  github.com/docker/libnetwork.(*sandbox).Delete()
      /home/moroz/project/workspace/src/github.com/docker/docker/vendor/src/github.com/docker/libnetwork/sandbox.go:191 +0x1047
  github.com/docker/docker/daemon.(*Daemon).releaseNetwork()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/container_unix.go:1180 +0x676
  github.com/docker/docker/daemon.(*Daemon).Cleanup()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/start.go:157 +0x5d
  github.com/docker/docker/daemon.(*containerMonitor).Close()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/monitor.go:111 +0xa4
  github.com/docker/docker/daemon.(*containerMonitor).Start.func1()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/monitor.go:142 +0x14b
  github.com/docker/docker/daemon.(*containerMonitor).Start()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/monitor.go:223 +0x1159
  github.com/docker/docker/daemon.(*containerMonitor).Start-fm()
      /home/moroz/project/workspace/src/github.com/docker/docker/daemon/start.go:147 +0x3b
  github.com/docker/docker/pkg/promise.Go.func1()
      /home/moroz/project/workspace/src/github.com/docker/docker/pkg/promise/promise.go:8 +0x2a

Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-12-18 13:14:25 -08:00
Jana Radhakrishnan 38cb40c4d4 Do not attempt serf query when not initialized
Sometimes, the vxlan kernel code may generate miss
notifications for vxlan bound packets when serf is
not initliazed. In such cases we should not try
doing a query as it will create a panic. We should
error out which will generate a log message.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
2015-12-18 13:14:09 -08:00
Madhu Venugopal 8da00fe824 Merge pull request #825 from mrjana/rel
CherryPick: Add overlay network support < 3.16 kernels
2015-12-17 16:18:28 -08:00
Jana Radhakrishnan 8f5e434276 Add overlay network support < 3.16 kernels
Add support for overlay networking in older kernels.

Following were done to achieve this:
    + Create the vxlan network in host namespace.
    + This may create conflicts with other private
      networks so check for conflicts and fail a
      join if there is any conflict.
    + Add iptable based filtering to only allow
      subnet bridges in the same network to forward
      traffic while different network bridges will
      not be able to forward b/w each other. Also
      block traffic to overlay network originating
      from the host itself.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
2015-12-17 15:35:17 -08:00
aboch 1c109b7c65 Merge pull request #791 from mavenugo/release-0.5
Cherry-picking a few fixes for 1.9.2
2015-11-30 13:54:44 -08:00
Jana Radhakrishnan 17e0f317d7 Fix a couple of edge cases in service discovery
The first issue is an ordering problem where sandbox
attached version of endpoint object should be pushed
to the watch database first so that any other create endpoint
which is in progress can make use of it immediately to update
the container hosts file. And only after that the current
container should try to retrieve the service records from the
service data base and upate it's hosts file. With the previous
order there is a small time window, when another endpoint create
will find this endpoint but it doesn't have the sandbox context
while the svc record population from svc db has already happened
so that container will totally miss to populate the service record
of the newly created endpoint.

The second issue is trying to rebuild the /etc/hosts file from scratch
during endpoint join and this may sometimes happen after the service
record add for another endpoint  has happened on the container
file. Obviously this rebuilding will wipe out that service record which
was just added. Removed the rebuilding of /etc/hosts file during
endpoint join. The initial population of /etc/hosts file should only
happen during sandbox creation time. In the endpoint join just added
the backward-compatible self ip -> hostname entry as just another
record.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
2015-11-30 13:22:46 -08:00
Alessandro Boch a3a2367267 Fix bug in bitsequence.pushReservation
- pushReservation fails to correctly detect when
  the affected block is the last in the current
  sequence. It thinks instead the block is in between
  the sequence. Because of this a couple of issues
  may happen:
   1. The allocation of the last bit causes the creation
      of a phantom sequence (length 0) at the end.
      (This has no side effects).
   2. The allocation of a bit somewhere in the middle of
      the bitmask may lead to a completely incorrect
      sequence pattern.

Signed-off-by: Alessandro Boch <aboch@docker.com>
2015-11-30 13:21:58 -08:00
David Manouchehri 7311b28766 English please. 2015-11-30 13:21:46 -08:00
Alessandro Boch 76f6e0898b Fix in endpoint Info() method
- Make sure to return the proper value for the
  EndpointInfo interface in case of nil implementer

Signed-off-by: Alessandro Boch <aboch@docker.com>
2015-11-30 13:21:26 -08:00
Madhu Venugopal f238be9000 Allow endpoint delete if sandbox identifier is stale
There are cases as seen in https://github.com/docker/docker/issues/17984
the sandbox could be stale in endpoint structure, when the actual
sandbox is removed during the cleanup phase. Hence instead of just
validating for sandboxID, make sure if it is actually present in the
sandboxes DB managed by the controller.

Signed-off-by: Madhu Venugopal <madhu@docker.com>
2015-11-30 13:20:18 -08:00
Alessandro Boch 8ac3b97ddf libnetwork to honor explicit mac-address
- Currently endpoint interface mac address is
  not being set in network.go when user specified
  the mac address for the container.
- Overlay driver expects to get the user defined mac-address
  from InterfaceInfo.

Signed-off-by: Alessandro Boch <aboch@docker.com>
2015-11-30 13:19:49 -08:00
Alexander Morozov e155f4eecc Remove redundant assignment to ep.network
Race detector was angry about that assignment

Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-11-30 13:19:04 -08:00
Alessandro Boch 1c123c389b Allow IPv6 allocation post endpoint create
- Controlled by network option

Signed-off-by: Alessandro Boch <aboch@docker.com>
2015-11-16 15:22:29 -08:00
Alessandro Boch 3eb77fb970 Fix bug in getAddressRange() in default ipam driver
- Callers expect to work with offsets based on master pool

Signed-off-by: Alessandro Boch <aboch@docker.com>
2015-11-16 15:21:00 -08:00
Zhang Wei 2652fb7fed Clean unused variables and fix typo
- Clean some unused variables
- format code.
- fix minor typo

Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
2015-11-16 15:20:44 -08:00
Alex Chan 71caa40f7a Tidy up the IPAM driver doc
Signed-off-by: Alex Chan <alex@alexwlchan.net>
2015-11-16 15:20:20 -08:00
Alessandro Boch 4b287a616a Fix bug in bridge driver
- On network delete, bridge interface removal is a best effort
  If netlink fails to remove the interface, we must not
  restore the network in the bridge network db

Signed-off-by: Alessandro Boch <aboch@docker.com>
2015-11-16 15:19:23 -08:00
Jana Radhakrishnan 7949ca05b5 Retain sandbox only if network is not available
It is sufficient to check only if network is available
in store to make the decision of whether to retain the
stale sandbox. If the endpoints are not available then
there is no point in retaining the sandbox anyways. This
fixes some extreme corner cases, where daemon goes down
right in the middle of sandbox cleanup happening.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
2015-11-16 15:18:52 -08:00
25 changed files with 927 additions and 224 deletions

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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) {

131
drivers/overlay/filter.go Normal file
View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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) {

View File

@ -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)
}

View File

@ -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)()

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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()
}

View File

@ -176,7 +176,6 @@ func (n *network) getEndpointsFromStore() ([]*endpoint, error) {
for _, kvo := range kvol {
ep := kvo.(*endpoint)
ep.network = n
epl = append(epl, ep)
}
}

View File

@ -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
}

View File

@ -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}

View File

@ -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
}

View File

@ -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"