Skip to content

Commit 917f30d

Browse files
committed
Verify network allocation on creating network
Current network allocation is run asynchronously with network (object) creation, when a user specifies an overlapped subnet, the network creation succeeds but this network is useless with empty IPAM config. This commit makes network creation use a channel to wait for the result of network allocation from Allocator before return. Because of this change, some _test.go files are modified. Also added TestCreateNetworkOverlapIP to network_test.go. Remove duplicated line in "make coverage" of direct.mk. Signed-off-by: Xinfeng Liu <xinfeng.liu@gmail.com>
1 parent d509e31 commit 917f30d

File tree

6 files changed

+101
-12
lines changed

6 files changed

+101
-12
lines changed

direct.mk

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ coverage: ## generate coverprofiles from the unit tests
124124
@echo "🐳 $@"
125125
@( for pkg in $(filter-out ${INTEGRATION_PACKAGE},${PACKAGES}); do \
126126
go test ${RACE} -tags "${DOCKER_BUILDTAGS}" -test.short -coverprofile="../../../$$pkg/coverage.txt" -covermode=atomic $$pkg || exit; \
127-
go test ${RACE} -tags "${DOCKER_BUILDTAGS}" -test.short -coverprofile="../../../$$pkg/coverage.txt" -covermode=atomic $$pkg || exit; \
128127
done )
129128

130129
.PHONY: coverage-integration

manager/allocator/network.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package allocator
33
import (
44
"context"
55
"fmt"
6+
"sync"
67
"time"
78

89
"github.com/docker/go-events"
@@ -28,6 +29,9 @@ var (
2829
errNoChanges = errors.New("task unchanged")
2930

3031
retryInterval = 5 * time.Minute
32+
33+
networkAllocationErrMap = make(map[string]chan error)
34+
mu sync.Mutex
3135
)
3236

3337
// Network context information which is used throughout the network allocation code.
@@ -67,6 +71,27 @@ type networkContext struct {
6771
somethingWasDeallocated bool
6872
}
6973

74+
// GetNetworkAllocationErrChan retrieves correspondent chan error given a network ID
75+
func GetNetworkAllocationErrChan(id string) chan error {
76+
mu.Lock()
77+
defer mu.Unlock()
78+
v, ok := networkAllocationErrMap[id]
79+
if !ok {
80+
v = make(chan error, 1) // don't want to block the sender
81+
networkAllocationErrMap[id] = v
82+
}
83+
return v
84+
}
85+
86+
// RemoveNetworkAllocationErrChan removes correspondent chan error given a network ID
87+
func RemoveNetworkAllocationErrChan(id string) {
88+
mu.Lock()
89+
defer mu.Unlock()
90+
if _, ok := networkAllocationErrMap[id]; ok {
91+
delete(networkAllocationErrMap, id)
92+
}
93+
}
94+
7095
func (a *Allocator) doNetworkInit(ctx context.Context) (err error) {
7196
var netConfig *cnmallocator.NetworkConfig
7297
// There are two ways user can invoke swarm init
@@ -179,28 +204,35 @@ func (a *Allocator) doNetworkAlloc(ctx context.Context, ev events.Event) {
179204
switch v := ev.(type) {
180205
case api.EventCreateNetwork:
181206
n := v.Network.Copy()
207+
errCh := GetNetworkAllocationErrChan(n.ID)
182208
if nc.nwkAllocator.IsAllocated(n) {
209+
errCh <- nil
183210
break
184211
}
185212
if IsIngressNetwork(n) && nc.ingressNetwork != nil {
186213
log.G(ctx).Errorf("Cannot allocate ingress network %s (%s) because another ingress network is already present: %s (%s)",
187214
n.ID, n.Spec.Annotations.Name, nc.ingressNetwork.ID, nc.ingressNetwork.Spec.Annotations.Name)
215+
errCh <- errors.Errorf("another ingress network is already present: %s", nc.ingressNetwork.Spec.Annotations.Name)
188216
break
189217
}
190218

191219
if err := a.allocateNetwork(ctx, n); err != nil {
192220
log.G(ctx).WithError(err).Errorf("Failed allocation for network %s", n.ID)
221+
errCh <- err
193222
break
194223
}
195224

196225
if err := a.store.Batch(func(batch *store.Batch) error {
197226
return a.commitAllocatedNetwork(ctx, batch, n)
198227
}); err != nil {
199228
log.G(ctx).WithError(err).Errorf("Failed to commit allocation for network %s", n.ID)
229+
errCh <- err
230+
break
200231
}
201232
if IsIngressNetwork(n) {
202233
nc.ingressNetwork = n
203234
}
235+
errCh <- nil
204236
case api.EventDeleteNetwork:
205237
n := v.Network.Copy()
206238

manager/controlapi/network.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/docker/libnetwork/ipamapi"
1010
"github.com/docker/swarmkit/api"
1111
"github.com/docker/swarmkit/identity"
12+
"github.com/docker/swarmkit/log"
1213
"github.com/docker/swarmkit/manager/allocator"
1314
"github.com/docker/swarmkit/manager/allocator/networkallocator"
1415
"github.com/docker/swarmkit/manager/state/store"
@@ -128,6 +129,17 @@ func (s *Server) CreateNetwork(ctx context.Context, request *api.CreateNetworkRe
128129
return nil, err
129130
}
130131

132+
// Waiting for network allocation result, remove the network in store if failure
133+
errCh := allocator.GetNetworkAllocationErrChan(n.ID)
134+
err = <-errCh
135+
allocator.RemoveNetworkAllocationErrChan(n.ID)
136+
if err != nil {
137+
if err1 := s.removeNetwork(n.ID); err1 != nil {
138+
log.G(ctx).Error(err1)
139+
}
140+
return nil, err
141+
}
142+
131143
return &api.CreateNetworkResponse{
132144
Network: n,
133145
}, nil

manager/controlapi/network_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ func createNetworkSpec(name string) *api.NetworkSpec {
2222
}
2323
}
2424

25+
func createOverlayNetworkSpecWithSubnet(name, subnet string) *api.NetworkSpec {
26+
spec := createNetworkSpec(name)
27+
spec.DriverConfig = &api.Driver{
28+
Name: "overlay",
29+
}
30+
spec.IPAM = &api.IPAMOptions{
31+
Configs: []*api.IPAMConfig{
32+
&api.IPAMConfig{
33+
Subnet: subnet,
34+
},
35+
},
36+
}
37+
return spec
38+
}
39+
2540
// createInternalNetwork creates an internal network for testing. it is the same
2641
// as Server.CreateNetwork except without the label check.
2742
func (s *Server) createInternalNetwork(ctx context.Context, request *api.CreateNetworkRequest) (*api.CreateNetworkResponse, error) {
@@ -209,6 +224,25 @@ func TestCreateNetworkInvalidDriver(t *testing.T) {
209224
assert.Error(t, err)
210225
}
211226

227+
func TestCreateNetworkOverlapIP(t *testing.T) {
228+
ts := newTestServer(t)
229+
defer ts.Stop()
230+
231+
//create first net
232+
spec := createOverlayNetworkSpecWithSubnet("overlaynet", "10.2.0.0/24")
233+
_, err := ts.Client.CreateNetwork(context.Background(), &api.CreateNetworkRequest{
234+
Spec: spec,
235+
})
236+
assert.NoError(t, err)
237+
238+
//create second net with overlap subnet
239+
spec2 := createOverlayNetworkSpecWithSubnet("overlaynet2", "10.2.0.0/24")
240+
_, err = ts.Client.CreateNetwork(context.Background(), &api.CreateNetworkRequest{
241+
Spec: spec2,
242+
})
243+
assert.Error(t, err)
244+
}
245+
212246
func TestListNetworks(t *testing.T) {
213247
ts := newTestServer(t)
214248
defer ts.Stop()

manager/controlapi/node_test.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,10 @@ func TestListNodesWithLabelFilter(t *testing.T) {
292292
// should only return the first 2 nodes
293293
assert.NoError(t, err)
294294
assert.Len(t, r.Nodes, 2)
295-
assert.Contains(t, r.Nodes, nodes[0])
296-
assert.Contains(t, r.Nodes, nodes[1])
295+
296+
// r.Nodes contains NetworkAttachment, so only compare ID here
297+
assert.Contains(t, []string{r.Nodes[0].ID, r.Nodes[1].ID}, nodes[0].ID)
298+
assert.Contains(t, []string{r.Nodes[0].ID, r.Nodes[1].ID}, nodes[1].ID)
297299

298300
t.Log("list nodes with enginelabel1=shouldmatch engine filter")
299301
r, err = ts.Client.ListNodes(context.Background(), &api.ListNodesRequest{
@@ -304,8 +306,8 @@ func TestListNodesWithLabelFilter(t *testing.T) {
304306
// should only return the first 2 nodes
305307
assert.NoError(t, err)
306308
assert.Len(t, r.Nodes, 2)
307-
assert.Contains(t, r.Nodes, nodes[0])
308-
assert.Contains(t, r.Nodes, nodes[1])
309+
assert.Contains(t, []string{r.Nodes[0].ID, r.Nodes[1].ID}, nodes[0].ID)
310+
assert.Contains(t, []string{r.Nodes[0].ID, r.Nodes[1].ID}, nodes[1].ID)
309311

310312
t.Log("list nodes with node two engine filters")
311313
r, err = ts.Client.ListNodes(context.Background(), &api.ListNodesRequest{
@@ -319,7 +321,7 @@ func TestListNodesWithLabelFilter(t *testing.T) {
319321
// should only return the first node
320322
assert.NoError(t, err)
321323
assert.Len(t, r.Nodes, 1)
322-
assert.Contains(t, r.Nodes, nodes[0])
324+
assert.Equal(t, r.Nodes[0].ID, nodes[0].ID)
323325

324326
t.Log("list nodes with node two node filters")
325327
r, err = ts.Client.ListNodes(context.Background(), &api.ListNodesRequest{
@@ -333,7 +335,7 @@ func TestListNodesWithLabelFilter(t *testing.T) {
333335
// should only return the first node
334336
assert.NoError(t, err)
335337
assert.Len(t, r.Nodes, 1)
336-
assert.Contains(t, r.Nodes, nodes[0])
338+
assert.Equal(t, r.Nodes[0].ID, nodes[0].ID)
337339

338340
t.Log("list nodes with both engine and node filters")
339341
r, err = ts.Client.ListNodes(context.Background(), &api.ListNodesRequest{
@@ -350,8 +352,8 @@ func TestListNodesWithLabelFilter(t *testing.T) {
350352
})
351353
assert.NoError(t, err)
352354
assert.Len(t, r.Nodes, 2)
353-
assert.Contains(t, r.Nodes, nodes[0])
354-
assert.Contains(t, r.Nodes, nodes[2])
355+
assert.Contains(t, []string{r.Nodes[0].ID, r.Nodes[1].ID}, nodes[0].ID)
356+
assert.Contains(t, []string{r.Nodes[0].ID, r.Nodes[1].ID}, nodes[2].ID)
355357
}
356358

357359
func TestRemoveNodes(t *testing.T) {

manager/controlapi/server_test.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ import (
1313
"github.com/docker/swarmkit/api"
1414
"github.com/docker/swarmkit/ca"
1515
cautils "github.com/docker/swarmkit/ca/testutils"
16+
"github.com/docker/swarmkit/manager/allocator"
1617
"github.com/docker/swarmkit/manager/state/store"
1718
stateutils "github.com/docker/swarmkit/manager/state/testutils"
1819
"github.com/stretchr/testify/assert"
1920
)
2021

2122
type testServer struct {
22-
Server *Server
23-
Client api.ControlClient
24-
Store *store.MemoryStore
23+
Server *Server
24+
Client api.ControlClient
25+
Store *store.MemoryStore
26+
Allocator *allocator.Allocator
2527

2628
grpcServer *grpc.Server
2729
clientConn *grpc.ClientConn
@@ -33,6 +35,7 @@ func (ts *testServer) Stop() {
3335
ts.clientConn.Close()
3436
ts.grpcServer.Stop()
3537
ts.Store.Close()
38+
ts.Allocator.Stop()
3639
os.RemoveAll(ts.tempUnixSocket)
3740
}
3841

@@ -48,6 +51,13 @@ func newTestServer(t *testing.T) *testServer {
4851
ts.Store = store.NewMemoryStore(&stateutils.MockProposer{})
4952
assert.NotNil(t, ts.Store)
5053

54+
ts.Allocator, err = allocator.New(ts.Store, nil, nil)
55+
assert.NoError(t, err)
56+
assert.NotNil(t, ts.Allocator)
57+
go func() {
58+
assert.NoError(t, ts.Allocator.Run(context.Background()))
59+
}()
60+
5161
ts.Server = NewServer(ts.Store, nil, securityConfig, nil, nil)
5262
assert.NotNil(t, ts.Server)
5363

0 commit comments

Comments
 (0)