github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/networking_cni_test.go (about)

     1  //go:build linux
     2  
     3  package allocrunner
     4  
     5  import (
     6  	"errors"
     7  	"net"
     8  	"testing"
     9  
    10  	"github.com/containerd/go-cni"
    11  	"github.com/hashicorp/nomad/ci"
    12  	"github.com/hashicorp/nomad/helper/testlog"
    13  	"github.com/shoenig/test/must"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  type mockIPTables struct {
    19  	listCall  [2]string
    20  	listRules []string
    21  	listErr   error
    22  
    23  	deleteCall [2]string
    24  	deleteErr  error
    25  
    26  	clearCall [2]string
    27  	clearErr  error
    28  }
    29  
    30  func (ipt *mockIPTables) List(table, chain string) ([]string, error) {
    31  	ipt.listCall[0], ipt.listCall[1] = table, chain
    32  	return ipt.listRules, ipt.listErr
    33  }
    34  
    35  func (ipt *mockIPTables) Delete(table, chain string, rule ...string) error {
    36  	ipt.deleteCall[0], ipt.deleteCall[1] = table, chain
    37  	return ipt.deleteErr
    38  }
    39  
    40  func (ipt *mockIPTables) ClearAndDeleteChain(table, chain string) error {
    41  	ipt.clearCall[0], ipt.clearCall[1] = table, chain
    42  	return ipt.clearErr
    43  }
    44  
    45  func (ipt *mockIPTables) assert(t *testing.T, jumpChain string) {
    46  	// List assertions
    47  	must.Eq(t, "nat", ipt.listCall[0])
    48  	must.Eq(t, "POSTROUTING", ipt.listCall[1])
    49  
    50  	// Delete assertions
    51  	must.Eq(t, "nat", ipt.deleteCall[0])
    52  	must.Eq(t, "POSTROUTING", ipt.deleteCall[1])
    53  
    54  	// Clear assertions
    55  	must.Eq(t, "nat", ipt.clearCall[0])
    56  	must.Eq(t, jumpChain, ipt.clearCall[1])
    57  }
    58  
    59  func TestCNI_forceCleanup(t *testing.T) {
    60  	t.Run("ok", func(t *testing.T) {
    61  		c := cniNetworkConfigurator{logger: testlog.HCLogger(t)}
    62  		ipt := &mockIPTables{
    63  			listRules: []string{
    64  				`-A POSTROUTING -m comment --comment "CNI portfwd requiring masquerade" -j CNI-HOSTPORT-MASQ`,
    65  				`-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE`,
    66  				`-A POSTROUTING -s 172.26.64.216/32 -m comment --comment "name: \"nomad\" id: \"79e8bf2e-a9c8-70ac-8d4e-fa5c4da99fbf\"" -j CNI-f2338c31d4de44472fe99c43`,
    67  				`-A POSTROUTING -s 172.26.64.217/32 -m comment --comment "name: \"nomad\" id: \"2dd71cac-2b1e-ff08-167c-735f7f9f4964\"" -j CNI-5d36f286cfbb35c5776509ec`,
    68  				`-A POSTROUTING -s 172.26.64.218/32 -m comment --comment "name: \"nomad\" id: \"5ff6deb7-9bc1-1491-f20c-e87b15de501d\"" -j CNI-2fe7686eac2fe43714a7b850`,
    69  				`-A POSTROUTING -m mark --mark 0x2000/0x2000 -j MASQUERADE`,
    70  				`-A POSTROUTING -m comment --comment "CNI portfwd masquerade mark" -j MARK --set-xmark 0x2000/0x2000`,
    71  			},
    72  		}
    73  		err := c.forceCleanup(ipt, "2dd71cac-2b1e-ff08-167c-735f7f9f4964")
    74  		must.NoError(t, err)
    75  		ipt.assert(t, "CNI-5d36f286cfbb35c5776509ec")
    76  	})
    77  
    78  	t.Run("missing allocation", func(t *testing.T) {
    79  		c := cniNetworkConfigurator{logger: testlog.HCLogger(t)}
    80  		ipt := &mockIPTables{
    81  			listRules: []string{
    82  				`-A POSTROUTING -m comment --comment "CNI portfwd requiring masquerade" -j CNI-HOSTPORT-MASQ`,
    83  				`-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE`,
    84  				`-A POSTROUTING -s 172.26.64.216/32 -m comment --comment "name: \"nomad\" id: \"79e8bf2e-a9c8-70ac-8d4e-fa5c4da99fbf\"" -j CNI-f2338c31d4de44472fe99c43`,
    85  				`-A POSTROUTING -s 172.26.64.217/32 -m comment --comment "name: \"nomad\" id: \"262d57a7-8f85-f3a4-9c3b-120c00ccbff1\"" -j CNI-5d36f286cfbb35c5776509ec`,
    86  				`-A POSTROUTING -s 172.26.64.218/32 -m comment --comment "name: \"nomad\" id: \"5ff6deb7-9bc1-1491-f20c-e87b15de501d\"" -j CNI-2fe7686eac2fe43714a7b850`,
    87  				`-A POSTROUTING -m mark --mark 0x2000/0x2000 -j MASQUERADE`,
    88  				`-A POSTROUTING -m comment --comment "CNI portfwd masquerade mark" -j MARK --set-xmark 0x2000/0x2000`,
    89  			},
    90  		}
    91  		err := c.forceCleanup(ipt, "2dd71cac-2b1e-ff08-167c-735f7f9f4964")
    92  		must.EqError(t, err, "failed to find postrouting rule for alloc 2dd71cac-2b1e-ff08-167c-735f7f9f4964")
    93  	})
    94  
    95  	t.Run("list error", func(t *testing.T) {
    96  		c := cniNetworkConfigurator{logger: testlog.HCLogger(t)}
    97  		ipt := &mockIPTables{listErr: errors.New("list error")}
    98  		err := c.forceCleanup(ipt, "2dd71cac-2b1e-ff08-167c-735f7f9f4964")
    99  		must.EqError(t, err, "failed to list iptables rules: list error")
   100  	})
   101  
   102  	t.Run("delete error", func(t *testing.T) {
   103  		c := cniNetworkConfigurator{logger: testlog.HCLogger(t)}
   104  		ipt := &mockIPTables{
   105  			deleteErr: errors.New("delete error"),
   106  			listRules: []string{
   107  				`-A POSTROUTING -s 172.26.64.217/32 -m comment --comment "name: \"nomad\" id: \"2dd71cac-2b1e-ff08-167c-735f7f9f4964\"" -j CNI-5d36f286cfbb35c5776509ec`,
   108  			},
   109  		}
   110  		err := c.forceCleanup(ipt, "2dd71cac-2b1e-ff08-167c-735f7f9f4964")
   111  		must.EqError(t, err, "failed to cleanup iptables rules for alloc 2dd71cac-2b1e-ff08-167c-735f7f9f4964")
   112  	})
   113  
   114  	t.Run("clear error", func(t *testing.T) {
   115  		c := cniNetworkConfigurator{logger: testlog.HCLogger(t)}
   116  		ipt := &mockIPTables{
   117  			clearErr: errors.New("clear error"),
   118  			listRules: []string{
   119  				`-A POSTROUTING -s 172.26.64.217/32 -m comment --comment "name: \"nomad\" id: \"2dd71cac-2b1e-ff08-167c-735f7f9f4964\"" -j CNI-5d36f286cfbb35c5776509ec`,
   120  			},
   121  		}
   122  		err := c.forceCleanup(ipt, "2dd71cac-2b1e-ff08-167c-735f7f9f4964")
   123  		must.EqError(t, err, "failed to cleanup iptables rules for alloc 2dd71cac-2b1e-ff08-167c-735f7f9f4964")
   124  	})
   125  }
   126  
   127  // TestCNI_cniToAllocNet_Fallback asserts if a CNI plugin result lacks an IP on
   128  // its sandbox interface, the first IP found is used.
   129  func TestCNI_cniToAllocNet_Fallback(t *testing.T) {
   130  	ci.Parallel(t)
   131  
   132  	// Calico's CNI plugin v3.12.3 has been observed to return the
   133  	// following:
   134  	cniResult := &cni.CNIResult{
   135  		Interfaces: map[string]*cni.Config{
   136  			"cali39179aa3-74": {},
   137  			"eth0": {
   138  				IPConfigs: []*cni.IPConfig{
   139  					{
   140  						IP: net.IPv4(192, 168, 135, 232),
   141  					},
   142  				},
   143  			},
   144  		},
   145  	}
   146  
   147  	// Only need a logger
   148  	c := &cniNetworkConfigurator{
   149  		logger: testlog.HCLogger(t),
   150  	}
   151  	allocNet, err := c.cniToAllocNet(cniResult)
   152  	require.NoError(t, err)
   153  	require.NotNil(t, allocNet)
   154  	assert.Equal(t, "192.168.135.232", allocNet.Address)
   155  	assert.Equal(t, "eth0", allocNet.InterfaceName)
   156  	assert.Nil(t, allocNet.DNS)
   157  }
   158  
   159  // TestCNI_cniToAllocNet_Invalid asserts an error is returned if a CNI plugin
   160  // result lacks any IP addresses. This has not been observed, but Nomad still
   161  // must guard against invalid results from external plugins.
   162  func TestCNI_cniToAllocNet_Invalid(t *testing.T) {
   163  	ci.Parallel(t)
   164  
   165  	cniResult := &cni.CNIResult{
   166  		Interfaces: map[string]*cni.Config{
   167  			"eth0": {},
   168  			"veth1": {
   169  				IPConfigs: []*cni.IPConfig{},
   170  			},
   171  		},
   172  	}
   173  
   174  	// Only need a logger
   175  	c := &cniNetworkConfigurator{
   176  		logger: testlog.HCLogger(t),
   177  	}
   178  	allocNet, err := c.cniToAllocNet(cniResult)
   179  	require.Error(t, err)
   180  	require.Nil(t, allocNet)
   181  }