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

     1  //go:build linux || windows
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package main
    20  
    21  import (
    22  	"fmt"
    23  	"io"
    24  	"net"
    25  	"regexp"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/containerd/nerdctl/pkg/testutil"
    30  	"github.com/containerd/nerdctl/pkg/testutil/nettestutil"
    31  	"gotest.tools/v3/assert"
    32  )
    33  
    34  // Tests various port mapping argument combinations by starting an nginx container and
    35  // verifying its connectivity and that its serves its index.html from the external
    36  // host IP as well as through the loopback interface.
    37  // `loopbackIsolationEnabled` indicates whether the test should expect connections between
    38  // the loopback interface and external host interface to succeed or not.
    39  func baseTestRunPort(t *testing.T, nginxImage string, nginxIndexHTMLSnippet string, loopbackIsolationEnabled bool) {
    40  	expectedIsolationErr := ""
    41  	if loopbackIsolationEnabled {
    42  		expectedIsolationErr = testutil.ExpectedConnectionRefusedError
    43  	}
    44  
    45  	hostIP, err := nettestutil.NonLoopbackIPv4()
    46  	assert.NilError(t, err)
    47  	type testCase struct {
    48  		listenIP         net.IP
    49  		connectIP        net.IP
    50  		hostPort         string
    51  		containerPort    string
    52  		connectURLPort   int
    53  		runShouldSuccess bool
    54  		err              string
    55  	}
    56  	lo := net.ParseIP("127.0.0.1")
    57  	zeroIP := net.ParseIP("0.0.0.0")
    58  	testCases := []testCase{
    59  		{
    60  			listenIP:         lo,
    61  			connectIP:        lo,
    62  			hostPort:         "8080",
    63  			containerPort:    "80",
    64  			connectURLPort:   8080,
    65  			runShouldSuccess: true,
    66  		},
    67  		{
    68  			// for https://github.com/containerd/nerdctl/issues/88
    69  			listenIP:         hostIP,
    70  			connectIP:        hostIP,
    71  			hostPort:         "8080",
    72  			containerPort:    "80",
    73  			connectURLPort:   8080,
    74  			runShouldSuccess: true,
    75  		},
    76  		{
    77  			listenIP:         hostIP,
    78  			connectIP:        lo,
    79  			hostPort:         "8080",
    80  			containerPort:    "80",
    81  			connectURLPort:   8080,
    82  			err:              expectedIsolationErr,
    83  			runShouldSuccess: true,
    84  		},
    85  		{
    86  			listenIP:         lo,
    87  			connectIP:        hostIP,
    88  			hostPort:         "8080",
    89  			containerPort:    "80",
    90  			connectURLPort:   8080,
    91  			err:              expectedIsolationErr,
    92  			runShouldSuccess: true,
    93  		},
    94  		{
    95  			listenIP:         zeroIP,
    96  			connectIP:        lo,
    97  			hostPort:         "8080",
    98  			containerPort:    "80",
    99  			connectURLPort:   8080,
   100  			runShouldSuccess: true,
   101  		},
   102  		{
   103  			listenIP:         zeroIP,
   104  			connectIP:        hostIP,
   105  			hostPort:         "8080",
   106  			containerPort:    "80",
   107  			connectURLPort:   8080,
   108  			runShouldSuccess: true,
   109  		},
   110  		{
   111  			listenIP:         lo,
   112  			connectIP:        lo,
   113  			hostPort:         "7000-7005",
   114  			containerPort:    "79-84",
   115  			connectURLPort:   7001,
   116  			runShouldSuccess: true,
   117  		},
   118  		{
   119  			listenIP:         hostIP,
   120  			connectIP:        hostIP,
   121  			hostPort:         "7000-7005",
   122  			containerPort:    "79-84",
   123  			connectURLPort:   7001,
   124  			runShouldSuccess: true,
   125  		},
   126  		{
   127  			listenIP:         hostIP,
   128  			connectIP:        lo,
   129  			hostPort:         "7000-7005",
   130  			containerPort:    "79-84",
   131  			connectURLPort:   7001,
   132  			err:              expectedIsolationErr,
   133  			runShouldSuccess: true,
   134  		},
   135  		{
   136  			listenIP:         lo,
   137  			connectIP:        hostIP,
   138  			hostPort:         "7000-7005",
   139  			containerPort:    "79-84",
   140  			connectURLPort:   7001,
   141  			err:              expectedIsolationErr,
   142  			runShouldSuccess: true,
   143  		},
   144  		{
   145  			listenIP:         zeroIP,
   146  			connectIP:        hostIP,
   147  			hostPort:         "7000-7005",
   148  			containerPort:    "79-84",
   149  			connectURLPort:   7001,
   150  			runShouldSuccess: true,
   151  		},
   152  		{
   153  			listenIP:         zeroIP,
   154  			connectIP:        lo,
   155  			hostPort:         "7000-7005",
   156  			containerPort:    "80-85",
   157  			connectURLPort:   7001,
   158  			err:              "error after 30 attempts",
   159  			runShouldSuccess: true,
   160  		},
   161  		{
   162  			listenIP:         zeroIP,
   163  			connectIP:        lo,
   164  			hostPort:         "7000-7005",
   165  			containerPort:    "80",
   166  			connectURLPort:   7000,
   167  			runShouldSuccess: true,
   168  		},
   169  		{
   170  			listenIP:         zeroIP,
   171  			connectIP:        lo,
   172  			hostPort:         "7000-7005",
   173  			containerPort:    "80",
   174  			connectURLPort:   7005,
   175  			err:              testutil.ExpectedConnectionRefusedError,
   176  			runShouldSuccess: true,
   177  		},
   178  		{
   179  			listenIP:         zeroIP,
   180  			connectIP:        lo,
   181  			hostPort:         "7000-7005",
   182  			containerPort:    "79-85",
   183  			connectURLPort:   7005,
   184  			err:              "invalid ranges specified for container and host Ports",
   185  			runShouldSuccess: false,
   186  		},
   187  	}
   188  
   189  	tID := testutil.Identifier(t)
   190  	for i, tc := range testCases {
   191  		i := i
   192  		tc := tc
   193  		tcName := fmt.Sprintf("%+v", tc)
   194  		t.Run(tcName, func(t *testing.T) {
   195  			testContainerName := fmt.Sprintf("%s-%d", tID, i)
   196  			base := testutil.NewBase(t)
   197  			defer base.Cmd("rm", "-f", testContainerName).Run()
   198  			pFlag := fmt.Sprintf("%s:%s:%s", tc.listenIP.String(), tc.hostPort, tc.containerPort)
   199  			connectURL := fmt.Sprintf("http://%s:%d", tc.connectIP.String(), tc.connectURLPort)
   200  			t.Logf("pFlag=%q, connectURL=%q", pFlag, connectURL)
   201  			cmd := base.Cmd("run", "-d",
   202  				"--name", testContainerName,
   203  				"-p", pFlag,
   204  				nginxImage)
   205  			if tc.runShouldSuccess {
   206  				cmd.AssertOK()
   207  			} else {
   208  				cmd.AssertFail()
   209  				return
   210  			}
   211  
   212  			resp, err := nettestutil.HTTPGet(connectURL, 30, false)
   213  			if tc.err != "" {
   214  				assert.ErrorContains(t, err, tc.err)
   215  				return
   216  			}
   217  			assert.NilError(t, err)
   218  			respBody, err := io.ReadAll(resp.Body)
   219  			assert.NilError(t, err)
   220  			assert.Assert(t, strings.Contains(string(respBody), nginxIndexHTMLSnippet))
   221  		})
   222  	}
   223  
   224  }
   225  
   226  func valuesOfMapStringString(m map[string]string) map[string]struct{} {
   227  	res := make(map[string]struct{})
   228  	for _, v := range m {
   229  		res[v] = struct{}{}
   230  	}
   231  	return res
   232  }
   233  
   234  func extractHostPort(portMapping string, port string) (string, error) {
   235  	// Regular expression to extract host port from port mapping information
   236  	re := regexp.MustCompile(`(?P<containerPort>\d{1,5})/tcp ->.*?0.0.0.0:(?P<hostPort>\d{1,5}).*?`)
   237  	portMappingLines := strings.Split(portMapping, "\n")
   238  	for _, portMappingLine := range portMappingLines {
   239  		// Find the matches
   240  		matches := re.FindStringSubmatch(portMappingLine)
   241  		// Check if there is a match
   242  		if len(matches) >= 3 && matches[1] == port {
   243  			// Extract the host port number
   244  			hostPort := matches[2]
   245  			return hostPort, nil
   246  		}
   247  	}
   248  	return "", fmt.Errorf("could not extract host port from port mapping: %s", portMapping)
   249  }