Browse code

Enabling ILB/ELB on windows using per-node, per-network LB endpoint.

Signed-off-by: Pradip Dhara <pradipd@microsoft.com>

Pradip Dhara authored on 2017/08/29 15:49:26
Showing 7 changed files
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	swarmtypes "github.com/docker/docker/api/types/swarm"
16 16
 	containerpkg "github.com/docker/docker/container"
17 17
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
18
+	networkSettings "github.com/docker/docker/daemon/network"
18 19
 	"github.com/docker/docker/plugin"
19 20
 	"github.com/docker/libnetwork"
20 21
 	"github.com/docker/libnetwork/cluster"
... ...
@@ -61,4 +62,5 @@ type Backend interface {
61 61
 	LookupImage(name string) (*types.ImageInspect, error)
62 62
 	PluginManager() *plugin.Manager
63 63
 	PluginGetter() *plugin.Store
64
+	GetLBAttachmentStore() *networkSettings.LBAttachmentStore
64 65
 }
... ...
@@ -136,23 +136,32 @@ func (e *executor) Describe(ctx context.Context) (*api.NodeDescription, error) {
136 136
 }
137 137
 
138 138
 func (e *executor) Configure(ctx context.Context, node *api.Node) error {
139
-	na := node.Attachment
140
-	if na == nil {
139
+	var ingressNA *api.NetworkAttachment
140
+	lbAttachments := make(map[string]string)
141
+
142
+	for _, na := range node.LbAttachments {
143
+		if na.Network.Spec.Ingress {
144
+			ingressNA = na
145
+		}
146
+		lbAttachments[na.Network.ID] = na.Addresses[0]
147
+	}
148
+
149
+	if ingressNA == nil {
141 150
 		e.backend.ReleaseIngress()
142
-		return nil
151
+		return e.backend.GetLBAttachmentStore().ResetLBAttachments(lbAttachments)
143 152
 	}
144 153
 
145 154
 	options := types.NetworkCreate{
146
-		Driver: na.Network.DriverState.Name,
155
+		Driver: ingressNA.Network.DriverState.Name,
147 156
 		IPAM: &network.IPAM{
148
-			Driver: na.Network.IPAM.Driver.Name,
157
+			Driver: ingressNA.Network.IPAM.Driver.Name,
149 158
 		},
150
-		Options:        na.Network.DriverState.Options,
159
+		Options:        ingressNA.Network.DriverState.Options,
151 160
 		Ingress:        true,
152 161
 		CheckDuplicate: true,
153 162
 	}
154 163
 
155
-	for _, ic := range na.Network.IPAM.Configs {
164
+	for _, ic := range ingressNA.Network.IPAM.Configs {
156 165
 		c := network.IPAMConfig{
157 166
 			Subnet:  ic.Subnet,
158 167
 			IPRange: ic.Range,
... ...
@@ -162,14 +171,17 @@ func (e *executor) Configure(ctx context.Context, node *api.Node) error {
162 162
 	}
163 163
 
164 164
 	_, err := e.backend.SetupIngress(clustertypes.NetworkCreateRequest{
165
-		ID: na.Network.ID,
165
+		ID: ingressNA.Network.ID,
166 166
 		NetworkCreateRequest: types.NetworkCreateRequest{
167
-			Name:          na.Network.Spec.Annotations.Name,
167
+			Name:          ingressNA.Network.Spec.Annotations.Name,
168 168
 			NetworkCreate: options,
169 169
 		},
170
-	}, na.Addresses[0])
170
+	}, ingressNA.Addresses[0])
171
+	if err != nil {
172
+		return err
173
+	}
171 174
 
172
-	return err
175
+	return e.backend.GetLBAttachmentStore().ResetLBAttachments(lbAttachments)
173 176
 }
174 177
 
175 178
 // Controller returns a docker container runner.
... ...
@@ -28,6 +28,7 @@ import (
28 28
 	"github.com/docker/docker/daemon/events"
29 29
 	"github.com/docker/docker/daemon/exec"
30 30
 	"github.com/docker/docker/daemon/logger"
31
+	"github.com/docker/docker/daemon/network"
31 32
 	"github.com/sirupsen/logrus"
32 33
 	// register graph drivers
33 34
 	_ "github.com/docker/docker/daemon/graphdriver/register"
... ...
@@ -121,6 +122,8 @@ type Daemon struct {
121 121
 	pruneRunning     int32
122 122
 	hosts            map[string]bool // hosts stores the addresses the daemon is listening on
123 123
 	startupDone      chan struct{}
124
+
125
+	lbAttachmentStore network.LBAttachmentStore
124 126
 }
125 127
 
126 128
 // StoreHosts stores the addresses the daemon is listening on
... ...
@@ -488,6 +491,8 @@ func (daemon *Daemon) DaemonLeavesCluster() {
488 488
 	} else {
489 489
 		logrus.Warnf("failed to initiate ingress network removal: %v", err)
490 490
 	}
491
+
492
+	daemon.lbAttachmentStore.ClearLBAttachments()
491 493
 }
492 494
 
493 495
 // setClusterProvider sets a component for querying the current cluster state.
... ...
@@ -1242,3 +1247,8 @@ func fixMemorySwappiness(resources *containertypes.Resources) {
1242 1242
 		resources.MemorySwappiness = nil
1243 1243
 	}
1244 1244
 }
1245
+
1246
+// GetLBAttachmentStore returns current load balancer store associated with the daemon
1247
+func (daemon *Daemon) GetLBAttachmentStore() *network.LBAttachmentStore {
1248
+	return &daemon.lbAttachmentStore
1249
+}
... ...
@@ -182,27 +182,8 @@ func (daemon *Daemon) setupIngress(create *clustertypes.NetworkCreateRequest, ip
182 182
 		logrus.Errorf("Failed getting ingress network by id after creating: %v", err)
183 183
 	}
184 184
 
185
-	sb, err := controller.NewSandbox("ingress-sbox", libnetwork.OptionIngress())
186
-	if err != nil {
187
-		if _, ok := err.(networktypes.ForbiddenError); !ok {
188
-			logrus.Errorf("Failed creating ingress sandbox: %v", err)
189
-		}
190
-		return
191
-	}
192
-
193
-	ep, err := n.CreateEndpoint("ingress-endpoint", libnetwork.CreateOptionIpam(ip, nil, nil, nil))
194
-	if err != nil {
195
-		logrus.Errorf("Failed creating ingress endpoint: %v", err)
196
-		return
197
-	}
198
-
199
-	if err := ep.Join(sb, nil); err != nil {
200
-		logrus.Errorf("Failed joining ingress sandbox to ingress endpoint: %v", err)
201
-		return
202
-	}
203
-
204
-	if err := sb.EnableService(); err != nil {
205
-		logrus.Errorf("Failed enabling service for ingress sandbox")
185
+	if err = daemon.createLoadBalancerSandbox("ingress", create.ID, ip, n, libnetwork.OptionIngress()); err != nil {
186
+		logrus.Errorf("Failed creating load balancer sandbox for ingress network: %v", err)
206 187
 	}
207 188
 }
208 189
 
... ...
@@ -283,6 +264,34 @@ func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.N
283 283
 	return resp, err
284 284
 }
285 285
 
286
+func (daemon *Daemon) createLoadBalancerSandbox(prefix, id string, ip net.IP, n libnetwork.Network, options ...libnetwork.SandboxOption) error {
287
+	c := daemon.netController
288
+	sandboxName := prefix + "-sbox"
289
+	sb, err := c.NewSandbox(sandboxName, options...)
290
+	if err != nil {
291
+		if _, ok := err.(networktypes.ForbiddenError); !ok {
292
+			return errors.Wrapf(err, "Failed creating %s sandbox", sandboxName)
293
+		}
294
+		return nil
295
+	}
296
+
297
+	endpointName := prefix + "-endpoint"
298
+	ep, err := n.CreateEndpoint(endpointName, libnetwork.CreateOptionIpam(ip, nil, nil, nil), libnetwork.CreateOptionLoadBalancer())
299
+	if err != nil {
300
+		return errors.Wrapf(err, "Failed creating %s in sandbox %s", endpointName, sandboxName)
301
+	}
302
+
303
+	if err := ep.Join(sb, nil); err != nil {
304
+		return errors.Wrapf(err, "Failed joining %s to sandbox %s", endpointName, sandboxName)
305
+	}
306
+
307
+	if err := sb.EnableService(); err != nil {
308
+		return errors.Wrapf(err, "Failed enabling service in %s sandbox", sandboxName)
309
+	}
310
+
311
+	return nil
312
+}
313
+
286 314
 func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string, agent bool) (*types.NetworkCreateResponse, error) {
287 315
 	if runconfig.IsPreDefinedNetwork(create.Name) && !agent {
288 316
 		err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name)
... ...
@@ -360,6 +369,18 @@ func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string
360 360
 	}
361 361
 	daemon.LogNetworkEvent(n, "create")
362 362
 
363
+	if agent && !n.Info().Ingress() && n.Type() == "overlay" {
364
+		nodeIP, exists := daemon.GetLBAttachmentStore().GetLBIPForNetwork(id)
365
+		if !exists {
366
+			return nil, fmt.Errorf("Failed to find a load balancer IP to use for network: %v", id)
367
+		}
368
+
369
+		if err := daemon.createLoadBalancerSandbox(create.Name, id, nodeIP, n); err != nil {
370
+			return nil, err
371
+		}
372
+
373
+	}
374
+
363 375
 	return &types.NetworkCreateResponse{
364 376
 		ID:      n.ID(),
365 377
 		Warning: warning,
... ...
@@ -496,6 +517,31 @@ func (daemon *Daemon) DeleteNetwork(networkID string) error {
496 496
 	return daemon.deleteNetwork(networkID, false)
497 497
 }
498 498
 
499
+func (daemon *Daemon) deleteLoadBalancerSandbox(n libnetwork.Network) {
500
+	controller := daemon.netController
501
+
502
+	//The only endpoint left should be the LB endpoint (nw.Name() + "-endpoint")
503
+	endpoints := n.Endpoints()
504
+	if len(endpoints) == 1 {
505
+		sandboxName := n.Name() + "-sbox"
506
+
507
+		if err := endpoints[0].Info().Sandbox().DisableService(); err != nil {
508
+			logrus.Errorf("Failed to disable service on sandbox %s: %v", sandboxName, err)
509
+			//Ignore error and attempt to delete the load balancer endpoint
510
+		}
511
+
512
+		if err := endpoints[0].Delete(true); err != nil {
513
+			logrus.Errorf("Failed to delete endpoint %s (%s) in %s: %v", endpoints[0].Name(), endpoints[0].ID(), sandboxName, err)
514
+			//Ignore error and attempt to delete the sandbox.
515
+		}
516
+
517
+		if err := controller.SandboxDestroy(sandboxName); err != nil {
518
+			logrus.Errorf("Failed to delete %s sandbox: %v", sandboxName, err)
519
+			//Ignore error and attempt to delete the network.
520
+		}
521
+	}
522
+}
523
+
499 524
 func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
500 525
 	nw, err := daemon.FindNetwork(networkID)
501 526
 	if err != nil {
... ...
@@ -517,6 +563,10 @@ func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
517 517
 		return notAllowedError{err}
518 518
 	}
519 519
 
520
+	if !nw.Info().Ingress() && nw.Type() == "overlay" {
521
+		daemon.deleteLoadBalancerSandbox(nw)
522
+	}
523
+
520 524
 	if err := nw.Delete(); err != nil {
521 525
 		return err
522 526
 	}
... ...
@@ -1,9 +1,12 @@
1 1
 package network
2 2
 
3 3
 import (
4
+	"net"
5
+
4 6
 	networktypes "github.com/docker/docker/api/types/network"
5 7
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
6 8
 	"github.com/docker/go-connections/nat"
9
+	"github.com/pkg/errors"
7 10
 )
8 11
 
9 12
 // Settings stores configuration details about the daemon network config
... ...
@@ -31,3 +34,36 @@ type EndpointSettings struct {
31 31
 	*networktypes.EndpointSettings
32 32
 	IPAMOperational bool
33 33
 }
34
+
35
+// LBAttachmentStore stores the load balancer IP address for a network id.
36
+type LBAttachmentStore struct {
37
+	//key: networkd id
38
+	//value: load balancer ip address
39
+	networkToNodeLBIP map[string]net.IP
40
+}
41
+
42
+// ResetLBAttachments clears any exsiting load balancer IP to network mapping and
43
+// sets the mapping to the given lbAttachments.
44
+func (lbStore *LBAttachmentStore) ResetLBAttachments(lbAttachments map[string]string) error {
45
+	lbStore.ClearLBAttachments()
46
+	for nid, nodeIP := range lbAttachments {
47
+		ip, _, err := net.ParseCIDR(nodeIP)
48
+		if err != nil {
49
+			lbStore.networkToNodeLBIP = make(map[string]net.IP)
50
+			return errors.Wrapf(err, "Failed to parse load balancer address %s", nodeIP)
51
+		}
52
+		lbStore.networkToNodeLBIP[nid] = ip
53
+	}
54
+	return nil
55
+}
56
+
57
+// ClearLBAttachments clears all the mappings of network to load balancer IP Address.
58
+func (lbStore *LBAttachmentStore) ClearLBAttachments() {
59
+	lbStore.networkToNodeLBIP = make(map[string]net.IP)
60
+}
61
+
62
+// GetLBIPForNetwork return the load balancer IP address for the given network.
63
+func (lbStore *LBAttachmentStore) GetLBIPForNetwork(networkID string) (net.IP, bool) {
64
+	ip, exists := lbStore.networkToNodeLBIP[networkID]
65
+	return ip, exists
66
+}
... ...
@@ -19,13 +19,21 @@ import (
19 19
 func pruneNetworkAndVerify(c *check.C, d *daemon.Swarm, kept, pruned []string) {
20 20
 	_, err := d.Cmd("network", "prune", "--force")
21 21
 	c.Assert(err, checker.IsNil)
22
-	out, err := d.Cmd("network", "ls", "--format", "{{.Name}}")
23
-	c.Assert(err, checker.IsNil)
22
+
24 23
 	for _, s := range kept {
25
-		c.Assert(out, checker.Contains, s)
24
+		waitAndAssert(c, defaultReconciliationTimeout, func(*check.C) (interface{}, check.CommentInterface) {
25
+			out, err := d.Cmd("network", "ls", "--format", "{{.Name}}")
26
+			c.Assert(err, checker.IsNil)
27
+			return out, nil
28
+		}, checker.Contains, s)
26 29
 	}
30
+
27 31
 	for _, s := range pruned {
28
-		c.Assert(out, checker.Not(checker.Contains), s)
32
+		waitAndAssert(c, defaultReconciliationTimeout, func(*check.C) (interface{}, check.CommentInterface) {
33
+			out, err := d.Cmd("network", "ls", "--format", "{{.Name}}")
34
+			c.Assert(err, checker.IsNil)
35
+			return out, nil
36
+		}, checker.Not(checker.Contains), s)
29 37
 	}
30 38
 }
31 39
 
... ...
@@ -64,6 +72,7 @@ func (s *DockerSwarmSuite) TestPruneNetwork(c *check.C) {
64 64
 	_, err = d.Cmd("service", "rm", serviceName)
65 65
 	c.Assert(err, checker.IsNil)
66 66
 	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 0)
67
+
67 68
 	pruneNetworkAndVerify(c, d, []string{}, []string{"n1", "n3"})
68 69
 }
69 70
 
70 71
new file mode 100644
... ...
@@ -0,0 +1,129 @@
0
+package service
1
+
2
+import (
3
+	"testing"
4
+	"time"
5
+
6
+	"github.com/docker/docker/api/types"
7
+	"github.com/docker/docker/api/types/filters"
8
+	"github.com/docker/docker/api/types/swarm"
9
+	"github.com/docker/docker/client"
10
+	"github.com/docker/docker/integration-cli/request"
11
+	"github.com/gotestyourself/gotestyourself/poll"
12
+	"github.com/stretchr/testify/assert"
13
+	"github.com/stretchr/testify/require"
14
+	"golang.org/x/net/context"
15
+)
16
+
17
+func TestCreateWithLBSandbox(t *testing.T) {
18
+	defer setupTest(t)()
19
+	d := newSwarm(t)
20
+	defer d.Stop(t)
21
+	client, err := request.NewClientForHost(d.Sock())
22
+	require.NoError(t, err)
23
+
24
+	overlayName := "overlay1"
25
+	networkCreate := types.NetworkCreate{
26
+		CheckDuplicate: true,
27
+		Driver:         "overlay",
28
+	}
29
+
30
+	netResp, err := client.NetworkCreate(context.Background(), overlayName, networkCreate)
31
+	require.NoError(t, err)
32
+	overlayID := netResp.ID
33
+
34
+	var instances uint64 = 1
35
+	serviceSpec := swarmServiceSpec("TestService", instances)
36
+
37
+	serviceSpec.TaskTemplate.Networks = append(serviceSpec.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{Target: overlayName})
38
+
39
+	serviceResp, err := client.ServiceCreate(context.Background(), serviceSpec, types.ServiceCreateOptions{
40
+		QueryRegistry: false,
41
+	})
42
+	require.NoError(t, err)
43
+
44
+	serviceID := serviceResp.ID
45
+	poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, instances))
46
+
47
+	_, _, err = client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
48
+	require.NoError(t, err)
49
+
50
+	network, err := client.NetworkInspect(context.Background(), overlayID, types.NetworkInspectOptions{})
51
+	require.NoError(t, err)
52
+	assert.Contains(t, network.Containers, overlayName+"-sbox")
53
+
54
+	err = client.ServiceRemove(context.Background(), serviceID)
55
+	require.NoError(t, err)
56
+
57
+	poll.WaitOn(t, serviceIsRemoved(client, serviceID))
58
+	err = client.NetworkRemove(context.Background(), overlayID)
59
+	require.NoError(t, err)
60
+
61
+	poll.WaitOn(t, networkIsRemoved(client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
62
+}
63
+
64
+func swarmServiceSpec(name string, replicas uint64) swarm.ServiceSpec {
65
+	return swarm.ServiceSpec{
66
+		Annotations: swarm.Annotations{
67
+			Name: name,
68
+		},
69
+		TaskTemplate: swarm.TaskSpec{
70
+			ContainerSpec: &swarm.ContainerSpec{
71
+				Image:   "busybox:latest",
72
+				Command: []string{"/bin/top"},
73
+			},
74
+		},
75
+		Mode: swarm.ServiceMode{
76
+			Replicated: &swarm.ReplicatedService{
77
+				Replicas: &replicas,
78
+			},
79
+		},
80
+	}
81
+}
82
+
83
+func serviceRunningTasksCount(client client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result {
84
+	return func(log poll.LogT) poll.Result {
85
+		filter := filters.NewArgs()
86
+		filter.Add("service", serviceID)
87
+		tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
88
+			Filters: filter,
89
+		})
90
+		switch {
91
+		case err != nil:
92
+			return poll.Error(err)
93
+		case len(tasks) == int(instances):
94
+			for _, task := range tasks {
95
+				if task.Status.State != swarm.TaskStateRunning {
96
+					return poll.Continue("waiting for tasks to enter run state")
97
+				}
98
+			}
99
+			return poll.Success()
100
+		default:
101
+			return poll.Continue("task count at %d waiting for %d", len(tasks), instances)
102
+		}
103
+	}
104
+}
105
+
106
+func serviceIsRemoved(client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
107
+	return func(log poll.LogT) poll.Result {
108
+		filter := filters.NewArgs()
109
+		filter.Add("service", serviceID)
110
+		_, err := client.TaskList(context.Background(), types.TaskListOptions{
111
+			Filters: filter,
112
+		})
113
+		if err == nil {
114
+			return poll.Continue("waiting for service %s to be deleted", serviceID)
115
+		}
116
+		return poll.Success()
117
+	}
118
+}
119
+
120
+func networkIsRemoved(client client.NetworkAPIClient, networkID string) func(log poll.LogT) poll.Result {
121
+	return func(log poll.LogT) poll.Result {
122
+		_, err := client.NetworkInspect(context.Background(), networkID, types.NetworkInspectOptions{})
123
+		if err == nil {
124
+			return poll.Continue("waiting for network %s to be removed", networkID)
125
+		}
126
+		return poll.Success()
127
+	}
128
+}