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 }