Signed-off-by: Pradip Dhara <pradipd@microsoft.com>
| ... | ... |
@@ -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 |
+} |