github.com/a4a881d4/docker@v1.9.0-rc2/integration-cli/docker_cli_network_unix_test.go (about)

     1  // +build !windows
     2  
     3  package main
     4  
     5  import (
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"os"
    13  	"strings"
    14  
    15  	"github.com/docker/docker/api/types"
    16  	"github.com/docker/docker/pkg/integration/checker"
    17  	"github.com/docker/libnetwork/driverapi"
    18  	remoteapi "github.com/docker/libnetwork/drivers/remote/api"
    19  	"github.com/docker/libnetwork/ipamapi"
    20  	remoteipam "github.com/docker/libnetwork/ipams/remote/api"
    21  	"github.com/docker/libnetwork/netlabel"
    22  	"github.com/go-check/check"
    23  )
    24  
    25  const dummyNetworkDriver = "dummy-network-driver"
    26  const dummyIpamDriver = "dummy-ipam-driver"
    27  
    28  var remoteDriverNetworkRequest remoteapi.CreateNetworkRequest
    29  
    30  func init() {
    31  	check.Suite(&DockerNetworkSuite{
    32  		ds: &DockerSuite{},
    33  	})
    34  }
    35  
    36  type DockerNetworkSuite struct {
    37  	server *httptest.Server
    38  	ds     *DockerSuite
    39  	d      *Daemon
    40  }
    41  
    42  func (s *DockerNetworkSuite) SetUpTest(c *check.C) {
    43  	s.d = NewDaemon(c)
    44  }
    45  
    46  func (s *DockerNetworkSuite) TearDownTest(c *check.C) {
    47  	s.d.Stop()
    48  	s.ds.TearDownTest(c)
    49  }
    50  
    51  func (s *DockerNetworkSuite) SetUpSuite(c *check.C) {
    52  	mux := http.NewServeMux()
    53  	s.server = httptest.NewServer(mux)
    54  	c.Assert(s.server, check.NotNil, check.Commentf("Failed to start a HTTP Server"))
    55  
    56  	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
    57  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
    58  		fmt.Fprintf(w, `{"Implements": ["%s", "%s"]}`, driverapi.NetworkPluginEndpointType, ipamapi.PluginEndpointType)
    59  	})
    60  
    61  	// Network driver implementation
    62  	mux.HandleFunc(fmt.Sprintf("/%s.GetCapabilities", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
    63  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
    64  		fmt.Fprintf(w, `{"Scope":"local"}`)
    65  	})
    66  
    67  	mux.HandleFunc(fmt.Sprintf("/%s.CreateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
    68  		err := json.NewDecoder(r.Body).Decode(&remoteDriverNetworkRequest)
    69  		if err != nil {
    70  			http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
    71  			return
    72  		}
    73  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
    74  		fmt.Fprintf(w, "null")
    75  	})
    76  
    77  	mux.HandleFunc(fmt.Sprintf("/%s.DeleteNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
    78  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
    79  		fmt.Fprintf(w, "null")
    80  	})
    81  
    82  	// Ipam Driver implementation
    83  	var (
    84  		poolRequest       remoteipam.RequestPoolRequest
    85  		poolReleaseReq    remoteipam.ReleasePoolRequest
    86  		addressRequest    remoteipam.RequestAddressRequest
    87  		addressReleaseReq remoteipam.ReleaseAddressRequest
    88  		lAS               = "localAS"
    89  		gAS               = "globalAS"
    90  		pool              = "172.28.0.0/16"
    91  		poolID            = lAS + "/" + pool
    92  		gw                = "172.28.255.254/16"
    93  	)
    94  
    95  	mux.HandleFunc(fmt.Sprintf("/%s.GetDefaultAddressSpaces", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
    96  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
    97  		fmt.Fprintf(w, `{"LocalDefaultAddressSpace":"`+lAS+`", "GlobalDefaultAddressSpace": "`+gAS+`"}`)
    98  	})
    99  
   100  	mux.HandleFunc(fmt.Sprintf("/%s.RequestPool", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
   101  		err := json.NewDecoder(r.Body).Decode(&poolRequest)
   102  		if err != nil {
   103  			http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
   104  			return
   105  		}
   106  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
   107  		if poolRequest.AddressSpace != lAS && poolRequest.AddressSpace != gAS {
   108  			fmt.Fprintf(w, `{"Error":"Unknown address space in pool request: `+poolRequest.AddressSpace+`"}`)
   109  		} else if poolRequest.Pool != "" && poolRequest.Pool != pool {
   110  			fmt.Fprintf(w, `{"Error":"Cannot handle explicit pool requests yet"}`)
   111  		} else {
   112  			fmt.Fprintf(w, `{"PoolID":"`+poolID+`", "Pool":"`+pool+`"}`)
   113  		}
   114  	})
   115  
   116  	mux.HandleFunc(fmt.Sprintf("/%s.RequestAddress", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
   117  		err := json.NewDecoder(r.Body).Decode(&addressRequest)
   118  		if err != nil {
   119  			http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
   120  			return
   121  		}
   122  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
   123  		// make sure libnetwork is now querying on the expected pool id
   124  		if addressRequest.PoolID != poolID {
   125  			fmt.Fprintf(w, `{"Error":"unknown pool id"}`)
   126  		} else if addressRequest.Address != "" {
   127  			fmt.Fprintf(w, `{"Error":"Cannot handle explicit address requests yet"}`)
   128  		} else {
   129  			fmt.Fprintf(w, `{"Address":"`+gw+`"}`)
   130  		}
   131  	})
   132  
   133  	mux.HandleFunc(fmt.Sprintf("/%s.ReleaseAddress", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
   134  		err := json.NewDecoder(r.Body).Decode(&addressReleaseReq)
   135  		if err != nil {
   136  			http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
   137  			return
   138  		}
   139  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
   140  		// make sure libnetwork is now asking to release the expected address fro mthe expected poolid
   141  		if addressRequest.PoolID != poolID {
   142  			fmt.Fprintf(w, `{"Error":"unknown pool id"}`)
   143  		} else if addressReleaseReq.Address != gw {
   144  			fmt.Fprintf(w, `{"Error":"unknown address"}`)
   145  		} else {
   146  			fmt.Fprintf(w, "null")
   147  		}
   148  	})
   149  
   150  	mux.HandleFunc(fmt.Sprintf("/%s.ReleasePool", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) {
   151  		err := json.NewDecoder(r.Body).Decode(&poolReleaseReq)
   152  		if err != nil {
   153  			http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest)
   154  			return
   155  		}
   156  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
   157  		// make sure libnetwork is now asking to release the expected poolid
   158  		if addressRequest.PoolID != poolID {
   159  			fmt.Fprintf(w, `{"Error":"unknown pool id"}`)
   160  		} else {
   161  			fmt.Fprintf(w, "null")
   162  		}
   163  	})
   164  
   165  	err := os.MkdirAll("/etc/docker/plugins", 0755)
   166  	c.Assert(err, checker.IsNil)
   167  
   168  	fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", dummyNetworkDriver)
   169  	err = ioutil.WriteFile(fileName, []byte(s.server.URL), 0644)
   170  	c.Assert(err, checker.IsNil)
   171  
   172  	ipamFileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", dummyIpamDriver)
   173  	err = ioutil.WriteFile(ipamFileName, []byte(s.server.URL), 0644)
   174  	c.Assert(err, checker.IsNil)
   175  }
   176  
   177  func (s *DockerNetworkSuite) TearDownSuite(c *check.C) {
   178  	if s.server == nil {
   179  		return
   180  	}
   181  
   182  	s.server.Close()
   183  
   184  	err := os.RemoveAll("/etc/docker/plugins")
   185  	c.Assert(err, checker.IsNil)
   186  }
   187  
   188  func assertNwIsAvailable(c *check.C, name string) {
   189  	if !isNwPresent(c, name) {
   190  		c.Fatalf("Network %s not found in network ls o/p", name)
   191  	}
   192  }
   193  
   194  func assertNwNotAvailable(c *check.C, name string) {
   195  	if isNwPresent(c, name) {
   196  		c.Fatalf("Found network %s in network ls o/p", name)
   197  	}
   198  }
   199  
   200  func isNwPresent(c *check.C, name string) bool {
   201  	out, _ := dockerCmd(c, "network", "ls")
   202  	lines := strings.Split(out, "\n")
   203  	for i := 1; i < len(lines)-1; i++ {
   204  		if strings.Contains(lines[i], name) {
   205  			return true
   206  		}
   207  	}
   208  	return false
   209  }
   210  
   211  func getNwResource(c *check.C, name string) *types.NetworkResource {
   212  	out, _ := dockerCmd(c, "network", "inspect", name)
   213  	nr := []types.NetworkResource{}
   214  	err := json.Unmarshal([]byte(out), &nr)
   215  	c.Assert(err, check.IsNil)
   216  	return &nr[0]
   217  }
   218  
   219  func (s *DockerNetworkSuite) TestDockerNetworkLsDefault(c *check.C) {
   220  	defaults := []string{"bridge", "host", "none"}
   221  	for _, nn := range defaults {
   222  		assertNwIsAvailable(c, nn)
   223  	}
   224  }
   225  
   226  func (s *DockerNetworkSuite) TestDockerNetworkCreateDelete(c *check.C) {
   227  	dockerCmd(c, "network", "create", "test")
   228  	assertNwIsAvailable(c, "test")
   229  
   230  	dockerCmd(c, "network", "rm", "test")
   231  	assertNwNotAvailable(c, "test")
   232  }
   233  
   234  func (s *DockerSuite) TestDockerInspectMultipleNetwork(c *check.C) {
   235  	out, _ := dockerCmd(c, "network", "inspect", "host", "none")
   236  	networkResources := []types.NetworkResource{}
   237  	err := json.Unmarshal([]byte(out), &networkResources)
   238  	c.Assert(err, check.IsNil)
   239  	c.Assert(networkResources, checker.HasLen, 2)
   240  
   241  	// Should print an error, return an exitCode 1 *but* should print the host network
   242  	out, exitCode, err := dockerCmdWithError("network", "inspect", "host", "nonexistent")
   243  	c.Assert(err, checker.NotNil)
   244  	c.Assert(exitCode, checker.Equals, 1)
   245  	c.Assert(out, checker.Contains, "Error: No such network: nonexistent")
   246  	networkResources = []types.NetworkResource{}
   247  	inspectOut := strings.SplitN(out, "\n", 2)[1]
   248  	err = json.Unmarshal([]byte(inspectOut), &networkResources)
   249  	c.Assert(networkResources, checker.HasLen, 1)
   250  
   251  	// Should print an error and return an exitCode, nothing else
   252  	out, exitCode, err = dockerCmdWithError("network", "inspect", "nonexistent")
   253  	c.Assert(err, checker.NotNil)
   254  	c.Assert(exitCode, checker.Equals, 1)
   255  	c.Assert(out, checker.Contains, "Error: No such network: nonexistent")
   256  }
   257  
   258  func (s *DockerNetworkSuite) TestDockerNetworkConnectDisconnect(c *check.C) {
   259  	dockerCmd(c, "network", "create", "test")
   260  	assertNwIsAvailable(c, "test")
   261  	nr := getNwResource(c, "test")
   262  
   263  	c.Assert(nr.Name, checker.Equals, "test")
   264  	c.Assert(len(nr.Containers), checker.Equals, 0)
   265  
   266  	// run a container
   267  	out, _ := dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
   268  	c.Assert(waitRun("test"), check.IsNil)
   269  	containerID := strings.TrimSpace(out)
   270  
   271  	// connect the container to the test network
   272  	dockerCmd(c, "network", "connect", "test", containerID)
   273  
   274  	// inspect the network to make sure container is connected
   275  	nr = getNetworkResource(c, nr.ID)
   276  	c.Assert(len(nr.Containers), checker.Equals, 1)
   277  	c.Assert(nr.Containers[containerID], check.NotNil)
   278  
   279  	// check if container IP matches network inspect
   280  	ip, _, err := net.ParseCIDR(nr.Containers[containerID].IPv4Address)
   281  	c.Assert(err, check.IsNil)
   282  	containerIP := findContainerIP(c, "test")
   283  	c.Assert(ip.String(), checker.Equals, containerIP)
   284  
   285  	// disconnect container from the network
   286  	dockerCmd(c, "network", "disconnect", "test", containerID)
   287  	nr = getNwResource(c, "test")
   288  	c.Assert(nr.Name, checker.Equals, "test")
   289  	c.Assert(len(nr.Containers), checker.Equals, 0)
   290  
   291  	// check if network connect fails for inactive containers
   292  	dockerCmd(c, "stop", containerID)
   293  	_, _, err = dockerCmdWithError("network", "connect", "test", containerID)
   294  	c.Assert(err, check.NotNil)
   295  
   296  	dockerCmd(c, "network", "rm", "test")
   297  	assertNwNotAvailable(c, "test")
   298  }
   299  
   300  func (s *DockerNetworkSuite) TestDockerNetworkIpamMultipleNetworks(c *check.C) {
   301  	// test0 bridge network
   302  	dockerCmd(c, "network", "create", "--subnet=192.168.0.0/16", "test1")
   303  	assertNwIsAvailable(c, "test1")
   304  
   305  	// test2 bridge network does not overlap
   306  	dockerCmd(c, "network", "create", "--subnet=192.169.0.0/16", "test2")
   307  	assertNwIsAvailable(c, "test2")
   308  
   309  	// for networks w/o ipam specified, docker will choose proper non-overlapping subnets
   310  	dockerCmd(c, "network", "create", "test3")
   311  	assertNwIsAvailable(c, "test3")
   312  	dockerCmd(c, "network", "create", "test4")
   313  	assertNwIsAvailable(c, "test4")
   314  	dockerCmd(c, "network", "create", "test5")
   315  	assertNwIsAvailable(c, "test5")
   316  
   317  	// test network with multiple subnets
   318  	// bridge network doesnt support multiple subnets. hence, use a dummy driver that supports
   319  
   320  	dockerCmd(c, "network", "create", "-d", dummyNetworkDriver, "--subnet=192.168.0.0/16", "--subnet=192.170.0.0/16", "test6")
   321  	assertNwIsAvailable(c, "test6")
   322  
   323  	// test network with multiple subnets with valid ipam combinations
   324  	// also check same subnet across networks when the driver supports it.
   325  	dockerCmd(c, "network", "create", "-d", dummyNetworkDriver,
   326  		"--subnet=192.168.0.0/16", "--subnet=192.170.0.0/16",
   327  		"--gateway=192.168.0.100", "--gateway=192.170.0.100",
   328  		"--ip-range=192.168.1.0/24",
   329  		"--aux-address", "a=192.168.1.5", "--aux-address", "b=192.168.1.6",
   330  		"--aux-address", "a=192.170.1.5", "--aux-address", "b=192.170.1.6",
   331  		"test7")
   332  	assertNwIsAvailable(c, "test7")
   333  
   334  	// cleanup
   335  	for i := 1; i < 8; i++ {
   336  		dockerCmd(c, "network", "rm", fmt.Sprintf("test%d", i))
   337  	}
   338  }
   339  
   340  func (s *DockerNetworkSuite) TestDockerNetworkCustomIpam(c *check.C) {
   341  	// Create a bridge network using custom ipam driver
   342  	dockerCmd(c, "network", "create", "--ipam-driver", dummyIpamDriver, "br0")
   343  	assertNwIsAvailable(c, "br0")
   344  
   345  	// Verify expected network ipam fields are there
   346  	nr := getNetworkResource(c, "br0")
   347  	c.Assert(nr.Driver, checker.Equals, "bridge")
   348  	c.Assert(nr.IPAM.Driver, checker.Equals, dummyIpamDriver)
   349  
   350  	// remove network and exercise remote ipam driver
   351  	dockerCmd(c, "network", "rm", "br0")
   352  	assertNwNotAvailable(c, "br0")
   353  }
   354  
   355  func (s *DockerNetworkSuite) TestDockerNetworkInspect(c *check.C) {
   356  	// if unspecified, network gateway will be selected from inside preferred pool
   357  	dockerCmd(c, "network", "create", "--driver=bridge", "--subnet=172.28.0.0/16", "--ip-range=172.28.5.0/24", "--gateway=172.28.5.254", "br0")
   358  	assertNwIsAvailable(c, "br0")
   359  
   360  	nr := getNetworkResource(c, "br0")
   361  	c.Assert(nr.Driver, checker.Equals, "bridge")
   362  	c.Assert(nr.Scope, checker.Equals, "local")
   363  	c.Assert(nr.IPAM.Driver, checker.Equals, "default")
   364  	c.Assert(len(nr.IPAM.Config), checker.Equals, 1)
   365  	c.Assert(nr.IPAM.Config[0].Subnet, checker.Equals, "172.28.0.0/16")
   366  	c.Assert(nr.IPAM.Config[0].IPRange, checker.Equals, "172.28.5.0/24")
   367  	c.Assert(nr.IPAM.Config[0].Gateway, checker.Equals, "172.28.5.254")
   368  	dockerCmd(c, "network", "rm", "br0")
   369  }
   370  
   371  func (s *DockerNetworkSuite) TestDockerNetworkIpamInvalidCombinations(c *check.C) {
   372  	// network with ip-range out of subnet range
   373  	_, _, err := dockerCmdWithError("network", "create", "--subnet=192.168.0.0/16", "--ip-range=192.170.0.0/16", "test")
   374  	c.Assert(err, check.NotNil)
   375  
   376  	// network with multiple gateways for a single subnet
   377  	_, _, err = dockerCmdWithError("network", "create", "--subnet=192.168.0.0/16", "--gateway=192.168.0.1", "--gateway=192.168.0.2", "test")
   378  	c.Assert(err, check.NotNil)
   379  
   380  	// Multiple overlaping subnets in the same network must fail
   381  	_, _, err = dockerCmdWithError("network", "create", "--subnet=192.168.0.0/16", "--subnet=192.168.1.0/16", "test")
   382  	c.Assert(err, check.NotNil)
   383  
   384  	// overlapping subnets across networks must fail
   385  	// create a valid test0 network
   386  	dockerCmd(c, "network", "create", "--subnet=192.168.0.0/16", "test0")
   387  	assertNwIsAvailable(c, "test0")
   388  	// create an overlapping test1 network
   389  	_, _, err = dockerCmdWithError("network", "create", "--subnet=192.168.128.0/17", "test1")
   390  	c.Assert(err, check.NotNil)
   391  	dockerCmd(c, "network", "rm", "test0")
   392  }
   393  
   394  func (s *DockerNetworkSuite) TestDockerNetworkDriverOptions(c *check.C) {
   395  	dockerCmd(c, "network", "create", "-d", dummyNetworkDriver, "-o", "opt1=drv1", "-o", "opt2=drv2", "testopt")
   396  	assertNwIsAvailable(c, "testopt")
   397  	gopts := remoteDriverNetworkRequest.Options[netlabel.GenericData]
   398  	c.Assert(gopts, checker.NotNil)
   399  	opts, ok := gopts.(map[string]interface{})
   400  	c.Assert(ok, checker.Equals, true)
   401  	c.Assert(opts["opt1"], checker.Equals, "drv1")
   402  	c.Assert(opts["opt2"], checker.Equals, "drv2")
   403  	dockerCmd(c, "network", "rm", "testopt")
   404  
   405  }
   406  
   407  func (s *DockerDaemonSuite) TestDockerNetworkDiscoveryICCFalse(c *check.C) {
   408  	// When icc == false, containers' etc/hosts should not be populated with containers' names
   409  	hostsFile := "/etc/hosts"
   410  	bridgeName := "external-bridge"
   411  	bridgeIP := "192.169.255.254/24"
   412  	out, err := createInterface(c, "bridge", bridgeName, bridgeIP)
   413  	c.Assert(err, check.IsNil, check.Commentf(out))
   414  	defer deleteInterface(c, bridgeName)
   415  
   416  	err = s.d.StartWithBusybox("--bridge", bridgeName, "--icc=false")
   417  	c.Assert(err, check.IsNil)
   418  	defer s.d.Restart()
   419  
   420  	// run two containers and store first container's etc/hosts content
   421  	out, err = s.d.Cmd("run", "-d", "busybox", "top")
   422  	c.Assert(err, check.IsNil)
   423  	cid1 := strings.TrimSpace(out)
   424  	defer s.d.Cmd("stop", cid1)
   425  
   426  	hosts, err := s.d.Cmd("exec", cid1, "cat", hostsFile)
   427  	c.Assert(err, checker.IsNil)
   428  
   429  	out, err = s.d.Cmd("run", "-d", "busybox", "top")
   430  	c.Assert(err, check.IsNil)
   431  	cid2 := strings.TrimSpace(out)
   432  
   433  	// verify first container's etc/hosts file has not changed after spawning second container
   434  	hostsPost, err := s.d.Cmd("exec", cid1, "cat", hostsFile)
   435  	c.Assert(err, checker.IsNil)
   436  	c.Assert(string(hosts), checker.Equals, string(hostsPost),
   437  		check.Commentf("Unexpected %s change on second container creation", hostsFile))
   438  
   439  	// stop container 2 and verify first container's etc/hosts has not changed
   440  	_, err = s.d.Cmd("stop", cid2)
   441  	c.Assert(err, check.IsNil)
   442  
   443  	hostsPost, err = s.d.Cmd("exec", cid1, "cat", hostsFile)
   444  	c.Assert(err, checker.IsNil)
   445  	c.Assert(string(hosts), checker.Equals, string(hostsPost),
   446  		check.Commentf("Unexpected %s change on second container creation", hostsFile))
   447  
   448  	// but discovery is on when connecting to non default bridge network
   449  	network := "anotherbridge"
   450  	out, err = s.d.Cmd("network", "create", network)
   451  	c.Assert(err, check.IsNil, check.Commentf(out))
   452  	defer s.d.Cmd("network", "rm", network)
   453  
   454  	out, err = s.d.Cmd("network", "connect", network, cid1)
   455  	c.Assert(err, check.IsNil, check.Commentf(out))
   456  
   457  	hostsPost, err = s.d.Cmd("exec", cid1, "cat", hostsFile)
   458  	c.Assert(err, checker.IsNil)
   459  	c.Assert(string(hosts), checker.Equals, string(hostsPost),
   460  		check.Commentf("Unexpected %s change on second network connection", hostsFile))
   461  
   462  	cName := "container3"
   463  	out, err = s.d.Cmd("run", "-d", "--net", network, "--name", cName, "busybox", "top")
   464  	c.Assert(err, check.IsNil, check.Commentf(out))
   465  	cid3 := strings.TrimSpace(out)
   466  	defer s.d.Cmd("stop", cid3)
   467  
   468  	// container1 etc/hosts file should contain an entry for the third container
   469  	hostsPost, err = s.d.Cmd("exec", cid1, "cat", hostsFile)
   470  	c.Assert(err, checker.IsNil)
   471  	c.Assert(string(hostsPost), checker.Contains, cName,
   472  		check.Commentf("Container 1  %s file does not contain entries for named container %q: %s", hostsFile, cName, string(hostsPost)))
   473  
   474  	// on container3 disconnect, first container's etc/hosts should go back to original form
   475  	out, err = s.d.Cmd("network", "disconnect", network, cid3)
   476  	c.Assert(err, check.IsNil, check.Commentf(out))
   477  
   478  	hostsPost, err = s.d.Cmd("exec", cid1, "cat", hostsFile)
   479  	c.Assert(err, checker.IsNil)
   480  	c.Assert(string(hosts), checker.Equals, string(hostsPost),
   481  		check.Commentf("Unexpected %s content after disconnecting from second network", hostsFile))
   482  }