github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/netutil/netutil_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 netutil
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"net"
    23  	"os"
    24  	"path/filepath"
    25  	"strconv"
    26  	"testing"
    27  	"text/template"
    28  
    29  	ncdefaults "github.com/containerd/nerdctl/v2/pkg/defaults"
    30  	"github.com/containerd/nerdctl/v2/pkg/labels"
    31  	"github.com/containerd/nerdctl/v2/pkg/testutil"
    32  
    33  	"gotest.tools/v3/assert"
    34  )
    35  
    36  const preExistingNetworkConfigTemplate = `
    37  {
    38      "cniVersion": "0.2.0",
    39      "name": "{{ .network_name }}",
    40      "type": "nat",
    41      "master": "Ethernet",
    42      "ipam": {
    43          "subnet": "{{ .subnet }}",
    44          "routes": [
    45              {
    46                  "GW": "{{ .gateway }}"
    47              }
    48          ]
    49      },
    50      "capabilities": {
    51          "portMappings": true,
    52          "dns": true
    53      }
    54  }
    55  `
    56  
    57  func TestParseIPAMRange(t *testing.T) {
    58  	t.Parallel()
    59  	type testCase struct {
    60  		subnet   string
    61  		gateway  string
    62  		iprange  string
    63  		expected *IPAMRange
    64  		err      string
    65  	}
    66  	testCases := []testCase{
    67  		{
    68  			subnet: "10.1.100.0/24",
    69  			expected: &IPAMRange{
    70  				Subnet:  "10.1.100.0/24",
    71  				Gateway: "10.1.100.1",
    72  			},
    73  		},
    74  		{
    75  			subnet:  "10.1.100.0/24",
    76  			gateway: "10.1.10.100",
    77  			err:     "no matching subnet",
    78  		},
    79  		{
    80  			subnet:  "10.1.100.0/24",
    81  			gateway: "10.1.100.100",
    82  			expected: &IPAMRange{
    83  				Subnet:  "10.1.100.0/24",
    84  				Gateway: "10.1.100.100",
    85  			},
    86  		},
    87  		{
    88  			subnet:  "10.1.100.0/23",
    89  			gateway: "10.1.102.1",
    90  			err:     "no matching subnet",
    91  		},
    92  		{
    93  			subnet:  "10.1.0.0/16",
    94  			iprange: "10.10.10.0/24",
    95  			err:     "no matching subnet",
    96  		},
    97  		{
    98  			subnet:  "10.1.0.0/16",
    99  			iprange: "10.1.100.0/24",
   100  			expected: &IPAMRange{
   101  				Subnet:     "10.1.0.0/16",
   102  				Gateway:    "10.1.0.1",
   103  				IPRange:    "10.1.100.0/24",
   104  				RangeStart: "10.1.100.1",
   105  				RangeEnd:   "10.1.100.255",
   106  			},
   107  		},
   108  		{
   109  			subnet:  "10.1.100.0/23",
   110  			iprange: "10.1.100.0/25",
   111  			expected: &IPAMRange{
   112  				Subnet:     "10.1.100.0/23",
   113  				Gateway:    "10.1.100.1",
   114  				IPRange:    "10.1.100.0/25",
   115  				RangeStart: "10.1.100.1",
   116  				RangeEnd:   "10.1.100.127",
   117  			},
   118  		},
   119  	}
   120  	for _, tc := range testCases {
   121  		_, subnet, _ := net.ParseCIDR(tc.subnet)
   122  		got, err := parseIPAMRange(subnet, tc.gateway, tc.iprange)
   123  		if tc.err != "" {
   124  			assert.ErrorContains(t, err, tc.err)
   125  		} else {
   126  			assert.NilError(t, err)
   127  			assert.Equal(t, *tc.expected, *got)
   128  		}
   129  	}
   130  }
   131  
   132  // Tests whether nerdctl properly creates the default network when required.
   133  // Note that this test will require a CNI driver bearing the same name as
   134  // the type of the default network. (denoted by netutil.DefaultNetworkName,
   135  // which is used as both the name of the default network and its Driver)
   136  func testDefaultNetworkCreation(t *testing.T) {
   137  	// To prevent subnet collisions when attempting to recreate the default network
   138  	// in the isolated CNI config dir we'll be using, we must first delete
   139  	// the network in the default CNI config dir.
   140  	defaultCniEnv := CNIEnv{
   141  		Path:        ncdefaults.CNIPath(),
   142  		NetconfPath: ncdefaults.CNINetConfPath(),
   143  	}
   144  	defaultNet, err := defaultCniEnv.GetDefaultNetworkConfig()
   145  	assert.NilError(t, err)
   146  	if defaultNet != nil {
   147  		assert.NilError(t, defaultCniEnv.RemoveNetwork(defaultNet))
   148  	}
   149  
   150  	// We create a tempdir for the CNI conf path to ensure an empty env for this test.
   151  	cniConfTestDir := t.TempDir()
   152  	cniEnv := CNIEnv{
   153  		Path:        ncdefaults.CNIPath(),
   154  		NetconfPath: cniConfTestDir,
   155  	}
   156  	// Ensure no default network config is not present.
   157  	defaultNetConf, err := cniEnv.GetDefaultNetworkConfig()
   158  	assert.NilError(t, err)
   159  	assert.Assert(t, defaultNetConf == nil)
   160  
   161  	// Attempt to create the default network.
   162  	err = cniEnv.ensureDefaultNetworkConfig()
   163  	assert.NilError(t, err)
   164  
   165  	// Ensure no default network config is present now.
   166  	defaultNetConf, err = cniEnv.GetDefaultNetworkConfig()
   167  	assert.NilError(t, err)
   168  	assert.Assert(t, defaultNetConf != nil)
   169  
   170  	// Check network config file present.
   171  	stat, err := os.Stat(defaultNetConf.File)
   172  	assert.NilError(t, err)
   173  	firstConfigModTime := stat.ModTime()
   174  
   175  	// Check default network label present.
   176  	assert.Assert(t, defaultNetConf.NerdctlLabels != nil)
   177  	lstr, ok := (*defaultNetConf.NerdctlLabels)[labels.NerdctlDefaultNetwork]
   178  	assert.Assert(t, ok)
   179  	boolv, err := strconv.ParseBool(lstr)
   180  	assert.NilError(t, err)
   181  	assert.Assert(t, boolv)
   182  
   183  	// Ensure network isn't created twice or accidentally re-created.
   184  	err = cniEnv.ensureDefaultNetworkConfig()
   185  	assert.NilError(t, err)
   186  
   187  	// Check for any other network config files.
   188  	files := []os.FileInfo{}
   189  	walkF := func(p string, info os.FileInfo, err error) error {
   190  		files = append(files, info)
   191  		return nil
   192  	}
   193  	err = filepath.Walk(cniConfTestDir, walkF)
   194  	assert.NilError(t, err)
   195  	assert.Assert(t, len(files) == 2) // files[0] is the entry for '.'
   196  	assert.Assert(t, filepath.Join(cniConfTestDir, files[1].Name()) == defaultNetConf.File)
   197  	assert.Assert(t, firstConfigModTime == files[1].ModTime())
   198  }
   199  
   200  // Tests whether nerdctl skips the creation of the default network if a
   201  // network bearing the default network name already exists in a
   202  // non-nerdctl-managed network config file.
   203  func TestNetworkWithDefaultNameAlreadyExists(t *testing.T) {
   204  	// We create a tempdir for the CNI conf path to ensure an empty env for this test.
   205  	cniConfTestDir := t.TempDir()
   206  	cniEnv := CNIEnv{
   207  		Path:        t.TempDir(), // irrelevant for this test
   208  		NetconfPath: cniConfTestDir,
   209  	}
   210  
   211  	// Ensure no default network config is not present.
   212  	defaultNetConf, err := cniEnv.GetDefaultNetworkConfig()
   213  	assert.NilError(t, err)
   214  	assert.Assert(t, defaultNetConf == nil)
   215  
   216  	// Manually define and write a network config file.
   217  	values := map[string]string{
   218  		"network_name": DefaultNetworkName,
   219  		"subnet":       "10.7.1.1/24",
   220  		"gateway":      "10.7.1.1",
   221  	}
   222  	tpl, err := template.New("test").Parse(preExistingNetworkConfigTemplate)
   223  	assert.NilError(t, err)
   224  	buf := &bytes.Buffer{}
   225  	assert.NilError(t, tpl.ExecuteTemplate(buf, "test", values))
   226  
   227  	// Filename is irrelevant as long as it's not nerdctl's.
   228  	testConfFile := filepath.Join(cniConfTestDir, fmt.Sprintf("%s.conf", testutil.Identifier(t)))
   229  	err = os.WriteFile(testConfFile, buf.Bytes(), 0600)
   230  	assert.NilError(t, err)
   231  
   232  	// Check network is detected.
   233  	netConfs, err := cniEnv.NetworkList()
   234  	assert.NilError(t, err)
   235  	assert.Assert(t, len(netConfs) > 0)
   236  
   237  	var listedDefaultNetConf *NetworkConfig
   238  	for _, netConf := range netConfs {
   239  		if netConf.Name == DefaultNetworkName {
   240  			listedDefaultNetConf = netConf
   241  			break
   242  		}
   243  	}
   244  	assert.Assert(t, listedDefaultNetConf != nil)
   245  
   246  	defaultNetConf, err = cniEnv.GetDefaultNetworkConfig()
   247  	assert.NilError(t, err)
   248  	assert.Assert(t, defaultNetConf != nil)
   249  	assert.Assert(t, defaultNetConf.File == testConfFile)
   250  
   251  	err = cniEnv.ensureDefaultNetworkConfig()
   252  	assert.NilError(t, err)
   253  
   254  	netConfs, err = cniEnv.NetworkList()
   255  	assert.NilError(t, err)
   256  	defaultNamedNetworksFileDefinitions := []string{}
   257  	for _, netConf := range netConfs {
   258  		if netConf.Name == DefaultNetworkName {
   259  			defaultNamedNetworksFileDefinitions = append(defaultNamedNetworksFileDefinitions, netConf.File)
   260  		}
   261  	}
   262  	assert.Assert(t, len(defaultNamedNetworksFileDefinitions) == 1)
   263  	assert.Assert(t, defaultNamedNetworksFileDefinitions[0] == testConfFile)
   264  }