github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_run_network_linux_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"net"
    23  	"regexp"
    24  	"runtime"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/containerd/errdefs"
    29  	"github.com/containerd/nerdctl/pkg/rootlessutil"
    30  	"github.com/containerd/nerdctl/pkg/testutil"
    31  	"github.com/containerd/nerdctl/pkg/testutil/nettestutil"
    32  	"gotest.tools/v3/assert"
    33  	"gotest.tools/v3/icmd"
    34  )
    35  
    36  // TestRunInternetConnectivity tests Internet connectivity with `apk update`
    37  func TestRunInternetConnectivity(t *testing.T) {
    38  	base := testutil.NewBase(t)
    39  	customNet := testutil.Identifier(t)
    40  	base.Cmd("network", "create", customNet).AssertOK()
    41  	defer base.Cmd("network", "rm", customNet).Run()
    42  
    43  	type testCase struct {
    44  		args []string
    45  	}
    46  	customNetID := base.InspectNetwork(customNet).ID
    47  	testCases := []testCase{
    48  		{
    49  			args: []string{"--net", "bridge"},
    50  		},
    51  		{
    52  			args: []string{"--net", customNet},
    53  		},
    54  		{
    55  			args: []string{"--net", customNetID},
    56  		},
    57  		{
    58  			args: []string{"--net", customNetID[:12]},
    59  		},
    60  		{
    61  			args: []string{"--net", "host"},
    62  		},
    63  	}
    64  	for _, tc := range testCases {
    65  		tc := tc // IMPORTANT
    66  		name := "default"
    67  		if len(tc.args) > 0 {
    68  			name = strings.Join(tc.args, "_")
    69  		}
    70  		t.Run(name, func(t *testing.T) {
    71  			args := []string{"run", "--rm"}
    72  			args = append(args, tc.args...)
    73  			args = append(args, testutil.AlpineImage, "apk", "update")
    74  			cmd := base.Cmd(args...)
    75  			cmd.AssertOutContains("OK")
    76  		})
    77  	}
    78  }
    79  
    80  // TestRunHostLookup tests hostname lookup
    81  func TestRunHostLookup(t *testing.T) {
    82  	base := testutil.NewBase(t)
    83  	// key: container name, val: network name
    84  	m := map[string]string{
    85  		"c0-in-n0":     "n0",
    86  		"c1-in-n0":     "n0",
    87  		"c2-in-n1":     "n1",
    88  		"c3-in-bridge": "bridge",
    89  	}
    90  	customNets := valuesOfMapStringString(m)
    91  	defer func() {
    92  		for name := range m {
    93  			base.Cmd("rm", "-f", name).Run()
    94  		}
    95  		for netName := range customNets {
    96  			if netName == "bridge" {
    97  				continue
    98  			}
    99  			base.Cmd("network", "rm", netName).Run()
   100  		}
   101  	}()
   102  
   103  	// Create networks
   104  	for netName := range customNets {
   105  		if netName == "bridge" {
   106  			continue
   107  		}
   108  		base.Cmd("network", "create", netName).AssertOK()
   109  	}
   110  
   111  	// Create nginx containers
   112  	for name, netName := range m {
   113  		cmd := base.Cmd("run",
   114  			"-d",
   115  			"--name", name,
   116  			"--hostname", name+"-foobar",
   117  			"--net", netName,
   118  			testutil.NginxAlpineImage,
   119  		)
   120  		t.Logf("creating host lookup testing container with command: %q", strings.Join(cmd.Command, " "))
   121  		cmd.AssertOK()
   122  	}
   123  
   124  	testWget := func(srcContainer, targetHostname string, expected bool) {
   125  		t.Logf("resolving %q in container %q (should success: %+v)", targetHostname, srcContainer, expected)
   126  		cmd := base.Cmd("exec", srcContainer, "wget", "-qO-", "http://"+targetHostname)
   127  		if expected {
   128  			cmd.AssertOutContains(testutil.NginxAlpineIndexHTMLSnippet)
   129  		} else {
   130  			cmd.AssertFail()
   131  		}
   132  	}
   133  
   134  	// Tests begin
   135  	testWget("c0-in-n0", "c1-in-n0", true)
   136  	testWget("c0-in-n0", "c1-in-n0.n0", true)
   137  	testWget("c0-in-n0", "c1-in-n0-foobar", true)
   138  	testWget("c0-in-n0", "c1-in-n0-foobar.n0", true)
   139  	testWget("c0-in-n0", "c2-in-n1", false)
   140  	testWget("c0-in-n0", "c2-in-n1.n1", false)
   141  	testWget("c0-in-n0", "c3-in-bridge", false)
   142  	testWget("c1-in-n0", "c0-in-n0", true)
   143  	testWget("c1-in-n0", "c0-in-n0.n0", true)
   144  	testWget("c1-in-n0", "c0-in-n0-foobar", true)
   145  	testWget("c1-in-n0", "c0-in-n0-foobar.n0", true)
   146  }
   147  
   148  func TestRunPortWithNoHostPort(t *testing.T) {
   149  	if rootlessutil.IsRootless() {
   150  		t.Skip("Auto port assign is not supported rootless mode yet")
   151  	}
   152  
   153  	type testCase struct {
   154  		containerPort    string
   155  		runShouldSuccess bool
   156  	}
   157  	testCases := []testCase{
   158  		{
   159  			containerPort:    "80",
   160  			runShouldSuccess: true,
   161  		},
   162  		{
   163  			containerPort:    "80-81",
   164  			runShouldSuccess: true,
   165  		},
   166  		{
   167  			containerPort:    "80-81/tcp",
   168  			runShouldSuccess: true,
   169  		},
   170  	}
   171  	tID := testutil.Identifier(t)
   172  	for i, tc := range testCases {
   173  		i := i
   174  		tc := tc
   175  		tcName := fmt.Sprintf("%+v", tc)
   176  		t.Run(tcName, func(t *testing.T) {
   177  			testContainerName := fmt.Sprintf("%s-%d", tID, i)
   178  			base := testutil.NewBase(t)
   179  			defer base.Cmd("rm", "-f", testContainerName).Run()
   180  			pFlag := tc.containerPort
   181  			cmd := base.Cmd("run", "-d",
   182  				"--name", testContainerName,
   183  				"-p", pFlag,
   184  				testutil.NginxAlpineImage)
   185  			var result *icmd.Result
   186  			stdoutContent := ""
   187  			if tc.runShouldSuccess {
   188  				cmd.AssertOK()
   189  			} else {
   190  				cmd.AssertFail()
   191  				return
   192  			}
   193  			portCmd := base.Cmd("port", testContainerName)
   194  			portCmd.Base.T.Helper()
   195  			result = portCmd.Run()
   196  			stdoutContent = result.Stdout() + result.Stderr()
   197  			assert.Assert(cmd.Base.T, result.ExitCode == 0, stdoutContent)
   198  			regexExpression := regexp.MustCompile(`80\/tcp.*?->.*?0.0.0.0:(?P<portNumber>\d{1,5}).*?`)
   199  			match := regexExpression.FindStringSubmatch(stdoutContent)
   200  			paramsMap := make(map[string]string)
   201  			for i, name := range regexExpression.SubexpNames() {
   202  				if i > 0 && i <= len(match) {
   203  					paramsMap[name] = match[i]
   204  				}
   205  			}
   206  			if _, ok := paramsMap["portNumber"]; !ok {
   207  				t.Fail()
   208  				return
   209  			}
   210  			connectURL := fmt.Sprintf("http://%s:%s", "127.0.0.1", paramsMap["portNumber"])
   211  			resp, err := nettestutil.HTTPGet(connectURL, 30, false)
   212  			assert.NilError(t, err)
   213  			respBody, err := io.ReadAll(resp.Body)
   214  			assert.NilError(t, err)
   215  			assert.Assert(t, strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet))
   216  		})
   217  	}
   218  
   219  }
   220  
   221  func TestUniqueHostPortAssignement(t *testing.T) {
   222  	if rootlessutil.IsRootless() {
   223  		t.Skip("Auto port assign is not supported rootless mode yet")
   224  	}
   225  
   226  	type testCase struct {
   227  		containerPort    string
   228  		runShouldSuccess bool
   229  	}
   230  
   231  	testCases := []testCase{
   232  		{
   233  			containerPort:    "80",
   234  			runShouldSuccess: true,
   235  		},
   236  		{
   237  			containerPort:    "80-81",
   238  			runShouldSuccess: true,
   239  		},
   240  		{
   241  			containerPort:    "80-81/tcp",
   242  			runShouldSuccess: true,
   243  		},
   244  	}
   245  
   246  	tID := testutil.Identifier(t)
   247  
   248  	for i, tc := range testCases {
   249  		i := i
   250  		tc := tc
   251  		tcName := fmt.Sprintf("%+v", tc)
   252  		t.Run(tcName, func(t *testing.T) {
   253  			testContainerName1 := fmt.Sprintf("%s-%d-1", tID, i)
   254  			testContainerName2 := fmt.Sprintf("%s-%d-2", tID, i)
   255  			base := testutil.NewBase(t)
   256  			defer base.Cmd("rm", "-f", testContainerName1, testContainerName2).Run()
   257  
   258  			pFlag := tc.containerPort
   259  			cmd1 := base.Cmd("run", "-d",
   260  				"--name", testContainerName1, "-p",
   261  				pFlag,
   262  				testutil.NginxAlpineImage)
   263  
   264  			cmd2 := base.Cmd("run", "-d",
   265  				"--name", testContainerName2, "-p",
   266  				pFlag,
   267  				testutil.NginxAlpineImage)
   268  			var result *icmd.Result
   269  			stdoutContent := ""
   270  			if tc.runShouldSuccess {
   271  				cmd1.AssertOK()
   272  				cmd2.AssertOK()
   273  			} else {
   274  				cmd1.AssertFail()
   275  				cmd2.AssertFail()
   276  				return
   277  			}
   278  			portCmd1 := base.Cmd("port", testContainerName1)
   279  			portCmd2 := base.Cmd("port", testContainerName2)
   280  			portCmd1.Base.T.Helper()
   281  			portCmd2.Base.T.Helper()
   282  			result = portCmd1.Run()
   283  			stdoutContent = result.Stdout() + result.Stderr()
   284  			assert.Assert(t, result.ExitCode == 0, stdoutContent)
   285  			port1, err := extractHostPort(stdoutContent, "80")
   286  			assert.NilError(t, err)
   287  			result = portCmd2.Run()
   288  			stdoutContent = result.Stdout() + result.Stderr()
   289  			assert.Assert(t, result.ExitCode == 0, stdoutContent)
   290  			port2, err := extractHostPort(stdoutContent, "80")
   291  			assert.NilError(t, err)
   292  			assert.Assert(t, port1 != port2, "Host ports are not unique")
   293  
   294  			// Make HTTP GET request to container 1
   295  			connectURL1 := fmt.Sprintf("http://%s:%s", "127.0.0.1", port1)
   296  			resp1, err := nettestutil.HTTPGet(connectURL1, 30, false)
   297  			assert.NilError(t, err)
   298  			respBody1, err := io.ReadAll(resp1.Body)
   299  			assert.NilError(t, err)
   300  			assert.Assert(t, strings.Contains(string(respBody1), testutil.NginxAlpineIndexHTMLSnippet))
   301  
   302  			// Make HTTP GET request to container 2
   303  			connectURL2 := fmt.Sprintf("http://%s:%s", "127.0.0.1", port2)
   304  			resp2, err := nettestutil.HTTPGet(connectURL2, 30, false)
   305  			assert.NilError(t, err)
   306  			respBody2, err := io.ReadAll(resp2.Body)
   307  			assert.NilError(t, err)
   308  			assert.Assert(t, strings.Contains(string(respBody2), testutil.NginxAlpineIndexHTMLSnippet))
   309  		})
   310  	}
   311  }
   312  
   313  func TestRunPort(t *testing.T) {
   314  	baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
   315  }
   316  
   317  func TestRunWithInvalidPortThenCleanUp(t *testing.T) {
   318  	// docker does not set label restriction to 4096 bytes
   319  	testutil.DockerIncompatible(t)
   320  	t.Parallel()
   321  	base := testutil.NewBase(t)
   322  	containerName := testutil.Identifier(t)
   323  	defer base.Cmd("rm", "-f", containerName).Run()
   324  	base.Cmd("run", "--rm", "--name", containerName, "-p", "22200-22299:22200-22299", testutil.CommonImage).AssertFail()
   325  	base.Cmd("run", "--rm", "--name", containerName, "-p", "22200-22299:22200-22299", testutil.CommonImage).AssertCombinedOutContains(errdefs.ErrInvalidArgument.Error())
   326  	base.Cmd("run", "--rm", "--name", containerName, testutil.CommonImage).AssertOK()
   327  }
   328  
   329  func TestRunContainerWithStaticIP(t *testing.T) {
   330  	if rootlessutil.IsRootless() {
   331  		t.Skip("Static IP assignment is not supported rootless mode yet.")
   332  	}
   333  	networkName := "test-network"
   334  	networkSubnet := "172.0.0.0/16"
   335  	base := testutil.NewBase(t)
   336  	cmd := base.Cmd("network", "create", networkName, "--subnet", networkSubnet)
   337  	cmd.AssertOK()
   338  	defer base.Cmd("network", "rm", networkName).Run()
   339  	testCases := []struct {
   340  		ip                string
   341  		shouldSuccess     bool
   342  		useNetwork        bool
   343  		checkTheIPAddress bool
   344  	}{
   345  		{
   346  			ip:                "172.0.0.2",
   347  			shouldSuccess:     true,
   348  			useNetwork:        true,
   349  			checkTheIPAddress: true,
   350  		},
   351  		{
   352  			ip:                "192.0.0.2",
   353  			shouldSuccess:     false,
   354  			useNetwork:        true,
   355  			checkTheIPAddress: false,
   356  		},
   357  		// XXX see https://github.com/containerd/nerdctl/issues/3101
   358  		// seems the incompatibility is coming from CNI plugin upgrade. This test has been disabled in main.
   359  		/*
   360  			{
   361  				ip:                "10.4.0.2",
   362  				shouldSuccess:     true,
   363  				useNetwork:        false,
   364  				checkTheIPAddress: false,
   365  			},
   366  		*/
   367  	}
   368  	tID := testutil.Identifier(t)
   369  	for i, tc := range testCases {
   370  		i := i
   371  		tc := tc
   372  		tcName := fmt.Sprintf("%+v", tc)
   373  		t.Run(tcName, func(t *testing.T) {
   374  			testContainerName := fmt.Sprintf("%s-%d", tID, i)
   375  			base := testutil.NewBase(t)
   376  			defer base.Cmd("rm", "-f", testContainerName).Run()
   377  			args := []string{
   378  				"run", "-d", "--name", testContainerName,
   379  			}
   380  			if tc.useNetwork {
   381  				args = append(args, []string{"--network", networkName}...)
   382  			}
   383  			args = append(args, []string{"--ip", tc.ip, testutil.NginxAlpineImage}...)
   384  			cmd := base.Cmd(args...)
   385  			if !tc.shouldSuccess {
   386  				cmd.AssertFail()
   387  				return
   388  			}
   389  			cmd.AssertOK()
   390  
   391  			if tc.checkTheIPAddress {
   392  				inspectCmd := base.Cmd("inspect", testContainerName, "--format", "\"{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}\"")
   393  				result := inspectCmd.Run()
   394  				stdoutContent := result.Stdout() + result.Stderr()
   395  				assert.Assert(inspectCmd.Base.T, result.ExitCode == 0, stdoutContent)
   396  				if !strings.Contains(stdoutContent, tc.ip) {
   397  					t.Fail()
   398  					return
   399  				}
   400  			}
   401  		})
   402  	}
   403  }
   404  
   405  func TestRunDNS(t *testing.T) {
   406  	base := testutil.NewBase(t)
   407  
   408  	base.Cmd("run", "--rm", "--dns", "8.8.8.8", testutil.CommonImage,
   409  		"cat", "/etc/resolv.conf").AssertOutContains("nameserver 8.8.8.8\n")
   410  	base.Cmd("run", "--rm", "--dns-search", "test", testutil.CommonImage,
   411  		"cat", "/etc/resolv.conf").AssertOutContains("search test\n")
   412  	base.Cmd("run", "--rm", "--dns-search", "test", "--dns-search", "test1", testutil.CommonImage,
   413  		"cat", "/etc/resolv.conf").AssertOutContains("search test test1\n")
   414  	base.Cmd("run", "--rm", "--dns-opt", "no-tld-query", "--dns-option", "attempts:10", testutil.CommonImage,
   415  		"cat", "/etc/resolv.conf").AssertOutContains("options no-tld-query attempts:10\n")
   416  	cmd := base.Cmd("run", "--rm", "--dns", "8.8.8.8", "--dns-search", "test", "--dns-option", "attempts:10", testutil.CommonImage,
   417  		"cat", "/etc/resolv.conf")
   418  	cmd.AssertOutContains("nameserver 8.8.8.8\n")
   419  	cmd.AssertOutContains("search test\n")
   420  	cmd.AssertOutContains("options attempts:10\n")
   421  }
   422  
   423  func TestSharedNetworkStack(t *testing.T) {
   424  	if runtime.GOOS != "linux" {
   425  		t.Skip("--network=container:<container name|id> only supports linux now")
   426  	}
   427  	base := testutil.NewBase(t)
   428  
   429  	containerName := testutil.Identifier(t)
   430  	defer base.Cmd("rm", "-f", containerName).AssertOK()
   431  	base.Cmd("run", "-d", "--name", containerName,
   432  		testutil.NginxAlpineImage).AssertOK()
   433  	base.EnsureContainerStarted(containerName)
   434  
   435  	containerNameJoin := testutil.Identifier(t) + "-network"
   436  	defer base.Cmd("rm", "-f", containerNameJoin).AssertOK()
   437  	base.Cmd("run",
   438  		"-d",
   439  		"--name", containerNameJoin,
   440  		"--network=container:"+containerName,
   441  		testutil.CommonImage,
   442  		"sleep", "infinity").AssertOK()
   443  
   444  	base.Cmd("exec", containerNameJoin, "wget", "-qO-", "http://127.0.0.1:80").
   445  		AssertOutContains(testutil.NginxAlpineIndexHTMLSnippet)
   446  
   447  	base.Cmd("restart", containerName).AssertOK()
   448  	base.Cmd("stop", "--time=1", containerNameJoin).AssertOK()
   449  	base.Cmd("start", containerNameJoin).AssertOK()
   450  	base.Cmd("exec", containerNameJoin, "wget", "-qO-", "http://127.0.0.1:80").
   451  		AssertOutContains(testutil.NginxAlpineIndexHTMLSnippet)
   452  }
   453  
   454  func TestRunContainerWithMACAddress(t *testing.T) {
   455  	base := testutil.NewBase(t)
   456  	tID := testutil.Identifier(t)
   457  	networkBridge := "testNetworkBridge" + tID
   458  	networkMACvlan := "testNetworkMACvlan" + tID
   459  	networkIPvlan := "testNetworkIPvlan" + tID
   460  	base.Cmd("network", "create", networkBridge, "--driver", "bridge").AssertOK()
   461  	base.Cmd("network", "create", networkMACvlan, "--driver", "macvlan").AssertOK()
   462  	base.Cmd("network", "create", networkIPvlan, "--driver", "ipvlan").AssertOK()
   463  	t.Cleanup(func() {
   464  		base.Cmd("network", "rm", networkBridge).Run()
   465  		base.Cmd("network", "rm", networkMACvlan).Run()
   466  		base.Cmd("network", "rm", networkIPvlan).Run()
   467  	})
   468  	tests := []struct {
   469  		Network string
   470  		WantErr bool
   471  		Expect  string
   472  	}{
   473  		{"host", true, "conflicting options"},
   474  		{"none", true, "can't open '/sys/class/net/eth0/address'"},
   475  		{"container:whatever" + tID, true, "conflicting options"},
   476  		{"bridge", false, ""},
   477  		{networkBridge, false, ""},
   478  		{networkMACvlan, false, ""},
   479  		{networkIPvlan, true, "not support"},
   480  	}
   481  	for _, test := range tests {
   482  		macAddress, err := nettestutil.GenerateMACAddress()
   483  		if err != nil {
   484  			t.Errorf("failed to generate MAC address: %s", err)
   485  		}
   486  		if test.Expect == "" && !test.WantErr {
   487  			test.Expect = macAddress
   488  		}
   489  		cmd := base.Cmd("run", "--rm", "--network", test.Network, "--mac-address", macAddress, testutil.CommonImage, "cat", "/sys/class/net/eth0/address")
   490  		if test.WantErr {
   491  			cmd.AssertFail()
   492  			cmd.AssertCombinedOutContains(test.Expect)
   493  		} else {
   494  			cmd.AssertOK()
   495  			cmd.AssertOutContains(test.Expect)
   496  		}
   497  	}
   498  }
   499  
   500  func TestHostsFileMounts(t *testing.T) {
   501  	base := testutil.NewBase(t)
   502  
   503  	base.Cmd("run", "--rm", testutil.CommonImage,
   504  		"sh", "-euxc", "echo >> /etc/hosts").AssertOK()
   505  	base.Cmd("run", "--rm", "--network", "host", testutil.CommonImage,
   506  		"sh", "-euxc", "echo >> /etc/hosts").AssertOK()
   507  	base.Cmd("run", "--rm", "-v", "/etc/hosts:/etc/hosts:ro", "--network", "host", testutil.CommonImage,
   508  		"sh", "-euxc", "echo >> /etc/hosts").AssertFail()
   509  	// add a line into /etc/hosts and remove it.
   510  	base.Cmd("run", "--rm", "-v", "/etc/hosts:/etc/hosts", "--network", "host", testutil.CommonImage,
   511  		"sh", "-euxc", "echo >> /etc/hosts").AssertOK()
   512  	base.Cmd("run", "--rm", "-v", "/etc/hosts:/etc/hosts", "--network", "host", testutil.CommonImage,
   513  		"sh", "-euxc", "head -n -1 /etc/hosts > temp && cat temp > /etc/hosts").AssertOK()
   514  
   515  	base.Cmd("run", "--rm", testutil.CommonImage,
   516  		"sh", "-euxc", "echo >> /etc/resolv.conf").AssertOK()
   517  	base.Cmd("run", "--rm", "--network", "host", testutil.CommonImage,
   518  		"sh", "-euxc", "echo >> /etc/resolv.conf").AssertOK()
   519  	base.Cmd("run", "--rm", "-v", "/etc/resolv.conf:/etc/resolv.conf:ro", "--network", "host", testutil.CommonImage,
   520  		"sh", "-euxc", "echo >> /etc/resolv.conf").AssertFail()
   521  	// add a line into /etc/resolv.conf and remove it.
   522  	base.Cmd("run", "--rm", "-v", "/etc/resolv.conf:/etc/resolv.conf", "--network", "host", testutil.CommonImage,
   523  		"sh", "-euxc", "echo >> /etc/resolv.conf").AssertOK()
   524  	base.Cmd("run", "--rm", "-v", "/etc/resolv.conf:/etc/resolv.conf", "--network", "host", testutil.CommonImage,
   525  		"sh", "-euxc", "head -n -1 /etc/resolv.conf > temp && cat temp > /etc/resolv.conf").AssertOK()
   526  }
   527  
   528  func TestRunContainerWithStaticIP6(t *testing.T) {
   529  	if rootlessutil.IsRootless() {
   530  		t.Skip("Static IP6 assignment is not supported rootless mode yet.")
   531  	}
   532  	networkName := "test-network"
   533  	networkSubnet := "2001:db8:5::/64"
   534  	_, subnet, err := net.ParseCIDR(networkSubnet)
   535  	assert.Assert(t, err == nil)
   536  	base := testutil.NewBaseWithIPv6Compatible(t)
   537  	base.Cmd("network", "create", networkName, "--subnet", networkSubnet, "--ipv6").AssertOK()
   538  	t.Cleanup(func() {
   539  		base.Cmd("network", "rm", networkName).Run()
   540  	})
   541  	testCases := []struct {
   542  		ip                string
   543  		shouldSuccess     bool
   544  		checkTheIPAddress bool
   545  	}{
   546  		{
   547  			ip:                "",
   548  			shouldSuccess:     true,
   549  			checkTheIPAddress: false,
   550  		},
   551  		{
   552  			ip:                "2001:db8:5::6",
   553  			shouldSuccess:     true,
   554  			checkTheIPAddress: true,
   555  		},
   556  		{
   557  			ip:                "2001:db8:4::6",
   558  			shouldSuccess:     false,
   559  			checkTheIPAddress: false,
   560  		},
   561  	}
   562  	tID := testutil.Identifier(t)
   563  	for i, tc := range testCases {
   564  		i := i
   565  		tc := tc
   566  		tcName := fmt.Sprintf("%+v", tc)
   567  		t.Run(tcName, func(t *testing.T) {
   568  			testContainerName := fmt.Sprintf("%s-%d", tID, i)
   569  			base := testutil.NewBaseWithIPv6Compatible(t)
   570  			args := []string{
   571  				"run", "--rm", "--name", testContainerName, "--network", networkName,
   572  			}
   573  			if tc.ip != "" {
   574  				args = append(args, "--ip6", tc.ip)
   575  			}
   576  			args = append(args, []string{testutil.NginxAlpineImage, "ip", "addr", "show", "dev", "eth0"}...)
   577  			cmd := base.Cmd(args...)
   578  			if !tc.shouldSuccess {
   579  				cmd.AssertFail()
   580  				return
   581  			}
   582  			cmd.AssertOutWithFunc(func(stdout string) error {
   583  				ip := findIPv6(stdout)
   584  				if !subnet.Contains(ip) {
   585  					return fmt.Errorf("expected subnet %s include ip %s", subnet, ip)
   586  				}
   587  				if tc.checkTheIPAddress {
   588  					if ip.String() != tc.ip {
   589  						return fmt.Errorf("expected ip %s, got %s", tc.ip, ip)
   590  					}
   591  				}
   592  				return nil
   593  			})
   594  		})
   595  	}
   596  }