kubenet_linux_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package kubenet
  14. import (
  15. "fmt"
  16. "net"
  17. "strings"
  18. "testing"
  19. "github.com/containernetworking/cni/libcni"
  20. "github.com/containernetworking/cni/pkg/types"
  21. "github.com/stretchr/testify/assert"
  22. "github.com/stretchr/testify/mock"
  23. utilsets "k8s.io/apimachinery/pkg/util/sets"
  24. kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
  25. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  26. "k8s.io/kubernetes/pkg/kubelet/dockershim/network"
  27. mockcni "k8s.io/kubernetes/pkg/kubelet/dockershim/network/cni/testing"
  28. hostporttest "k8s.io/kubernetes/pkg/kubelet/dockershim/network/hostport/testing"
  29. nettest "k8s.io/kubernetes/pkg/kubelet/dockershim/network/testing"
  30. "k8s.io/kubernetes/pkg/util/bandwidth"
  31. ipttest "k8s.io/kubernetes/pkg/util/iptables/testing"
  32. sysctltest "k8s.io/kubernetes/pkg/util/sysctl/testing"
  33. "k8s.io/utils/exec"
  34. fakeexec "k8s.io/utils/exec/testing"
  35. )
  36. // test it fulfills the NetworkPlugin interface
  37. var _ network.NetworkPlugin = &kubenetNetworkPlugin{}
  38. func newFakeKubenetPlugin(initMap map[kubecontainer.ContainerID]utilsets.String, execer exec.Interface, host network.Host) *kubenetNetworkPlugin {
  39. return &kubenetNetworkPlugin{
  40. podIPs: initMap,
  41. execer: execer,
  42. mtu: 1460,
  43. host: host,
  44. }
  45. }
  46. func TestGetPodNetworkStatus(t *testing.T) {
  47. podIPMap := make(map[kubecontainer.ContainerID]utilsets.String)
  48. podIPMap[kubecontainer.ContainerID{ID: "1"}] = utilsets.NewString("10.245.0.2")
  49. podIPMap[kubecontainer.ContainerID{ID: "2"}] = utilsets.NewString("10.245.0.3")
  50. podIPMap[kubecontainer.ContainerID{ID: "3"}] = utilsets.NewString("10.245.0.4", "2000::")
  51. podIPMap[kubecontainer.ContainerID{ID: "4"}] = utilsets.NewString("2000::2")
  52. testCases := []struct {
  53. id string
  54. expectError bool
  55. expectIP utilsets.String
  56. }{
  57. //in podCIDR map
  58. {
  59. id: "1",
  60. expectError: false,
  61. expectIP: utilsets.NewString("10.245.0.2"),
  62. },
  63. {
  64. id: "2",
  65. expectError: false,
  66. expectIP: utilsets.NewString("10.245.0.3"),
  67. },
  68. {
  69. id: "3",
  70. expectError: false,
  71. expectIP: utilsets.NewString("10.245.0.4", "2000::"),
  72. },
  73. {
  74. id: "4",
  75. expectError: false,
  76. expectIP: utilsets.NewString("2000::2"),
  77. },
  78. //not in podIP map
  79. {
  80. id: "does-not-exist-map",
  81. expectError: true,
  82. expectIP: nil,
  83. },
  84. //TODO: add test cases for retrieving ip inside container network namespace
  85. }
  86. fakeCmds := make([]fakeexec.FakeCommandAction, 0)
  87. for _, t := range testCases {
  88. // the fake commands return the IP from the given index, or an error
  89. fCmd := fakeexec.FakeCmd{
  90. CombinedOutputScript: []fakeexec.FakeAction{
  91. func() ([]byte, []byte, error) {
  92. ips, ok := podIPMap[kubecontainer.ContainerID{ID: t.id}]
  93. if !ok {
  94. return nil, nil, fmt.Errorf("Pod IP %q not found", t.id)
  95. }
  96. ipsList := ips.UnsortedList()
  97. return []byte(ipsList[0]), nil, nil
  98. },
  99. },
  100. }
  101. fakeCmds = append(fakeCmds, func(cmd string, args ...string) exec.Cmd {
  102. return fakeexec.InitFakeCmd(&fCmd, cmd, args...)
  103. })
  104. }
  105. fexec := fakeexec.FakeExec{
  106. CommandScript: fakeCmds,
  107. LookPathFunc: func(file string) (string, error) {
  108. return fmt.Sprintf("/fake-bin/%s", file), nil
  109. },
  110. }
  111. fhost := nettest.NewFakeHost(nil)
  112. fakeKubenet := newFakeKubenetPlugin(podIPMap, &fexec, fhost)
  113. for i, tc := range testCases {
  114. out, err := fakeKubenet.GetPodNetworkStatus("", "", kubecontainer.ContainerID{ID: tc.id})
  115. if tc.expectError {
  116. if err == nil {
  117. t.Errorf("Test case %d expects error but got none", i)
  118. }
  119. continue
  120. } else {
  121. if err != nil {
  122. t.Errorf("Test case %d expects error but got error: %v", i, err)
  123. }
  124. }
  125. seen := make(map[string]bool)
  126. allExpected := tc.expectIP.UnsortedList()
  127. for _, expectedIP := range allExpected {
  128. for _, outIP := range out.IPs {
  129. if expectedIP == outIP.String() {
  130. seen[expectedIP] = true
  131. break
  132. }
  133. }
  134. }
  135. if len(tc.expectIP) != len(seen) {
  136. t.Errorf("Test case %d expects ip %s but got %s", i, tc.expectIP, out.IP.String())
  137. }
  138. }
  139. }
  140. // TestTeardownCallsShaper tests that a `TearDown` call does call
  141. // `shaper.Reset`
  142. func TestTeardownCallsShaper(t *testing.T) {
  143. fexec := &fakeexec.FakeExec{
  144. CommandScript: []fakeexec.FakeCommandAction{},
  145. LookPathFunc: func(file string) (string, error) {
  146. return fmt.Sprintf("/fake-bin/%s", file), nil
  147. },
  148. }
  149. fhost := nettest.NewFakeHost(nil)
  150. fshaper := &bandwidth.FakeShaper{}
  151. mockcni := &mockcni.MockCNI{}
  152. ips := make(map[kubecontainer.ContainerID]utilsets.String)
  153. kubenet := newFakeKubenetPlugin(ips, fexec, fhost)
  154. kubenet.loConfig = &libcni.NetworkConfig{
  155. Network: &types.NetConf{
  156. Name: "loopback-fake",
  157. Type: "loopback",
  158. },
  159. }
  160. kubenet.cniConfig = mockcni
  161. kubenet.iptables = ipttest.NewFake()
  162. kubenet.bandwidthShaper = fshaper
  163. kubenet.hostportSyncer = hostporttest.NewFakeHostportSyncer()
  164. mockcni.On("DelNetwork", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*libcni.NetworkConfig"), mock.AnythingOfType("*libcni.RuntimeConf")).Return(nil)
  165. details := make(map[string]interface{})
  166. details[network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR] = "10.0.0.1/24"
  167. kubenet.Event(network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE, details)
  168. existingContainerID := kubecontainer.BuildContainerID("docker", "123")
  169. kubenet.podIPs[existingContainerID] = utilsets.NewString("10.0.0.1")
  170. if err := kubenet.TearDownPod("namespace", "name", existingContainerID); err != nil {
  171. t.Fatalf("Unexpected error in TearDownPod: %v", err)
  172. }
  173. assert.Equal(t, []string{"10.0.0.1/32"}, fshaper.ResetCIDRs, "shaper.Reset should have been called")
  174. mockcni.AssertExpectations(t)
  175. }
  176. // TestInit tests that a `Init` call with an MTU sets the MTU
  177. func TestInit_MTU(t *testing.T) {
  178. var fakeCmds []fakeexec.FakeCommandAction
  179. {
  180. // modprobe br-netfilter
  181. fCmd := fakeexec.FakeCmd{
  182. CombinedOutputScript: []fakeexec.FakeAction{
  183. func() ([]byte, []byte, error) {
  184. return make([]byte, 0), nil, nil
  185. },
  186. },
  187. }
  188. fakeCmds = append(fakeCmds, func(cmd string, args ...string) exec.Cmd {
  189. return fakeexec.InitFakeCmd(&fCmd, cmd, args...)
  190. })
  191. }
  192. fexec := &fakeexec.FakeExec{
  193. CommandScript: fakeCmds,
  194. LookPathFunc: func(file string) (string, error) {
  195. return fmt.Sprintf("/fake-bin/%s", file), nil
  196. },
  197. }
  198. fhost := nettest.NewFakeHost(nil)
  199. ips := make(map[kubecontainer.ContainerID]utilsets.String)
  200. kubenet := newFakeKubenetPlugin(ips, fexec, fhost)
  201. kubenet.iptables = ipttest.NewFake()
  202. sysctl := sysctltest.NewFake()
  203. sysctl.Settings["net/bridge/bridge-nf-call-iptables"] = 0
  204. kubenet.sysctl = sysctl
  205. if err := kubenet.Init(nettest.NewFakeHost(nil), kubeletconfig.HairpinNone, "10.0.0.0/8", 1234); err != nil {
  206. t.Fatalf("Unexpected error in Init: %v", err)
  207. }
  208. assert.Equal(t, 1234, kubenet.mtu, "kubenet.mtu should have been set")
  209. assert.Equal(t, 1, sysctl.Settings["net/bridge/bridge-nf-call-iptables"], "net/bridge/bridge-nf-call-iptables sysctl should have been set")
  210. }
  211. // TestInvocationWithoutRuntime invokes the plugin without a runtime.
  212. // This is how kubenet is invoked from the cri.
  213. func TestTearDownWithoutRuntime(t *testing.T) {
  214. testCases := []struct {
  215. podCIDR []string
  216. expectedPodCIDR []string
  217. ip string
  218. }{
  219. {
  220. podCIDR: []string{"10.0.0.0/24"},
  221. expectedPodCIDR: []string{"10.0.0.0/24"},
  222. ip: "10.0.0.1",
  223. },
  224. {
  225. podCIDR: []string{"10.0.0.1/24"},
  226. expectedPodCIDR: []string{"10.0.0.0/24"},
  227. ip: "10.0.0.1",
  228. },
  229. {
  230. podCIDR: []string{"2001:beef::/48"},
  231. expectedPodCIDR: []string{"2001:beef::/48"},
  232. ip: "2001:beef::1",
  233. },
  234. {
  235. podCIDR: []string{"2001:beef::1/48"},
  236. expectedPodCIDR: []string{"2001:beef::/48"},
  237. ip: "2001:beef::1",
  238. },
  239. }
  240. for _, tc := range testCases {
  241. fhost := nettest.NewFakeHost(nil)
  242. fhost.Legacy = false
  243. mockcni := &mockcni.MockCNI{}
  244. fexec := &fakeexec.FakeExec{
  245. CommandScript: []fakeexec.FakeCommandAction{},
  246. LookPathFunc: func(file string) (string, error) {
  247. return fmt.Sprintf("/fake-bin/%s", file), nil
  248. },
  249. }
  250. ips := make(map[kubecontainer.ContainerID]utilsets.String)
  251. kubenet := newFakeKubenetPlugin(ips, fexec, fhost)
  252. kubenet.loConfig = &libcni.NetworkConfig{
  253. Network: &types.NetConf{
  254. Name: "loopback-fake",
  255. Type: "loopback",
  256. },
  257. }
  258. kubenet.cniConfig = mockcni
  259. kubenet.iptables = ipttest.NewFake()
  260. details := make(map[string]interface{})
  261. details[network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR] = strings.Join(tc.podCIDR, ",")
  262. kubenet.Event(network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE, details)
  263. if len(kubenet.podCIDRs) != len(tc.podCIDR) {
  264. t.Errorf("generated podCidr: %q, expecting: %q are not of the same length", kubenet.podCIDRs, tc.podCIDR)
  265. continue
  266. }
  267. for idx := range tc.podCIDR {
  268. if kubenet.podCIDRs[idx].String() != tc.expectedPodCIDR[idx] {
  269. t.Errorf("generated podCidr: %q, expecting: %q", kubenet.podCIDRs[idx].String(), tc.expectedPodCIDR[idx])
  270. }
  271. }
  272. existingContainerID := kubecontainer.BuildContainerID("docker", "123")
  273. kubenet.podIPs[existingContainerID] = utilsets.NewString(tc.ip)
  274. mockcni.On("DelNetwork", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*libcni.NetworkConfig"), mock.AnythingOfType("*libcni.RuntimeConf")).Return(nil)
  275. if err := kubenet.TearDownPod("namespace", "name", existingContainerID); err != nil {
  276. t.Fatalf("Unexpected error in TearDownPod: %v", err)
  277. }
  278. // Assert that the CNI DelNetwork made it through and we didn't crash
  279. // without a runtime.
  280. mockcni.AssertExpectations(t)
  281. }
  282. }
  283. func TestGetRoutesConfig(t *testing.T) {
  284. for _, test := range []struct {
  285. cidrs []string
  286. routes string
  287. }{
  288. {
  289. cidrs: []string{"10.0.0.1/24"},
  290. routes: `{"dst": "0.0.0.0/0"}`,
  291. },
  292. {
  293. cidrs: []string{"2001:4860:4860::8888/32"},
  294. routes: `{"dst": "::/0"}`,
  295. },
  296. {
  297. cidrs: []string{"2001:4860:4860::8888/32", "10.0.0.1/24"},
  298. routes: `{"dst": "0.0.0.0/0"},{"dst": "::/0"}`,
  299. },
  300. } {
  301. var cidrs []*net.IPNet
  302. for _, c := range test.cidrs {
  303. _, cidr, err := net.ParseCIDR(c)
  304. assert.NoError(t, err)
  305. cidrs = append(cidrs, cidr)
  306. }
  307. fakeKubenet := &kubenetNetworkPlugin{podCIDRs: cidrs}
  308. assert.Equal(t, test.routes, fakeKubenet.getRoutesConfig())
  309. }
  310. }
  311. func TestGetRangesConfig(t *testing.T) {
  312. for _, test := range []struct {
  313. cidrs []string
  314. ranges string
  315. }{
  316. {
  317. cidrs: []string{"10.0.0.0/24"},
  318. ranges: `
  319. [{
  320. "subnet": "10.0.0.0/24"
  321. }]`,
  322. },
  323. {
  324. cidrs: []string{"2001:4860::/32"},
  325. ranges: `
  326. [{
  327. "subnet": "2001:4860::/32"
  328. }]`,
  329. },
  330. {
  331. cidrs: []string{"10.0.0.0/24", "2001:4860::/32"},
  332. ranges: `
  333. [{
  334. "subnet": "10.0.0.0/24"
  335. }],
  336. [{
  337. "subnet": "2001:4860::/32"
  338. }]`,
  339. },
  340. } {
  341. var cidrs []*net.IPNet
  342. for _, c := range test.cidrs {
  343. _, cidr, err := net.ParseCIDR(c)
  344. assert.NoError(t, err)
  345. cidrs = append(cidrs, cidr)
  346. }
  347. fakeKubenet := &kubenetNetworkPlugin{podCIDRs: cidrs}
  348. assert.Equal(t, test.ranges, fakeKubenet.getRangesConfig())
  349. }
  350. }
  351. //TODO: add unit test for each implementation of network plugin interface