server_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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 app
  14. import (
  15. "errors"
  16. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "path/filepath"
  20. "reflect"
  21. "runtime"
  22. "strings"
  23. "testing"
  24. "time"
  25. "github.com/stretchr/testify/assert"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/util/diff"
  28. componentbaseconfig "k8s.io/component-base/config"
  29. kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config"
  30. "k8s.io/kubernetes/pkg/util/configz"
  31. utilpointer "k8s.io/utils/pointer"
  32. )
  33. type fakeIPTablesVersioner struct {
  34. version string // what to return
  35. err error // what to return
  36. }
  37. func (fake *fakeIPTablesVersioner) GetVersion() (string, error) {
  38. return fake.version, fake.err
  39. }
  40. func (fake *fakeIPTablesVersioner) IsCompatible() error {
  41. return fake.err
  42. }
  43. type fakeIPSetVersioner struct {
  44. version string // what to return
  45. err error // what to return
  46. }
  47. func (fake *fakeIPSetVersioner) GetVersion() (string, error) {
  48. return fake.version, fake.err
  49. }
  50. type fakeKernelCompatTester struct {
  51. ok bool
  52. }
  53. func (fake *fakeKernelCompatTester) IsCompatible() error {
  54. if !fake.ok {
  55. return fmt.Errorf("error")
  56. }
  57. return nil
  58. }
  59. // fakeKernelHandler implements KernelHandler.
  60. type fakeKernelHandler struct {
  61. modules []string
  62. }
  63. func (fake *fakeKernelHandler) GetModules() ([]string, error) {
  64. return fake.modules, nil
  65. }
  66. // This test verifies that NewProxyServer does not crash when CleanupAndExit is true.
  67. func TestProxyServerWithCleanupAndExit(t *testing.T) {
  68. // Each bind address below is a separate test case
  69. bindAddresses := []string{
  70. "0.0.0.0",
  71. "::",
  72. }
  73. for _, addr := range bindAddresses {
  74. options := NewOptions()
  75. options.config = &kubeproxyconfig.KubeProxyConfiguration{
  76. BindAddress: addr,
  77. }
  78. options.CleanupAndExit = true
  79. proxyserver, err := NewProxyServer(options)
  80. assert.Nil(t, err, "unexpected error in NewProxyServer, addr: %s", addr)
  81. assert.NotNil(t, proxyserver, "nil proxy server obj, addr: %s", addr)
  82. assert.NotNil(t, proxyserver.IptInterface, "nil iptables intf, addr: %s", addr)
  83. // Clean up config for next test case
  84. configz.Delete(kubeproxyconfig.GroupName)
  85. }
  86. }
  87. func TestGetConntrackMax(t *testing.T) {
  88. ncores := runtime.NumCPU()
  89. testCases := []struct {
  90. min int32
  91. maxPerCore int32
  92. expected int
  93. err string
  94. }{
  95. {
  96. expected: 0,
  97. },
  98. {
  99. maxPerCore: 67890, // use this if Max is 0
  100. min: 1, // avoid 0 default
  101. expected: 67890 * ncores,
  102. },
  103. {
  104. maxPerCore: 1, // ensure that Min is considered
  105. min: 123456,
  106. expected: 123456,
  107. },
  108. {
  109. maxPerCore: 0, // leave system setting
  110. min: 123456,
  111. expected: 0,
  112. },
  113. }
  114. for i, tc := range testCases {
  115. cfg := kubeproxyconfig.KubeProxyConntrackConfiguration{
  116. Min: utilpointer.Int32Ptr(tc.min),
  117. MaxPerCore: utilpointer.Int32Ptr(tc.maxPerCore),
  118. }
  119. x, e := getConntrackMax(cfg)
  120. if e != nil {
  121. if tc.err == "" {
  122. t.Errorf("[%d] unexpected error: %v", i, e)
  123. } else if !strings.Contains(e.Error(), tc.err) {
  124. t.Errorf("[%d] expected an error containing %q: %v", i, tc.err, e)
  125. }
  126. } else if x != tc.expected {
  127. t.Errorf("[%d] expected %d, got %d", i, tc.expected, x)
  128. }
  129. }
  130. }
  131. // TestLoadConfig tests proper operation of loadConfig()
  132. func TestLoadConfig(t *testing.T) {
  133. yamlTemplate := `apiVersion: kubeproxy.config.k8s.io/v1alpha1
  134. bindAddress: %s
  135. clientConnection:
  136. acceptContentTypes: "abc"
  137. burst: 100
  138. contentType: content-type
  139. kubeconfig: "/path/to/kubeconfig"
  140. qps: 7
  141. clusterCIDR: "%s"
  142. configSyncPeriod: 15s
  143. conntrack:
  144. maxPerCore: 2
  145. min: 1
  146. tcpCloseWaitTimeout: 10s
  147. tcpEstablishedTimeout: 20s
  148. healthzBindAddress: "%s"
  149. hostnameOverride: "foo"
  150. iptables:
  151. masqueradeAll: true
  152. masqueradeBit: 17
  153. minSyncPeriod: 10s
  154. syncPeriod: 60s
  155. ipvs:
  156. minSyncPeriod: 10s
  157. syncPeriod: 60s
  158. excludeCIDRs:
  159. - "10.20.30.40/16"
  160. - "fd00:1::0/64"
  161. kind: KubeProxyConfiguration
  162. metricsBindAddress: "%s"
  163. mode: "%s"
  164. oomScoreAdj: 17
  165. portRange: "2-7"
  166. resourceContainer: /foo
  167. udpIdleTimeout: 123ms
  168. nodePortAddresses:
  169. - "10.20.30.40/16"
  170. - "fd00:1::0/64"
  171. `
  172. testCases := []struct {
  173. name string
  174. mode string
  175. bindAddress string
  176. clusterCIDR string
  177. healthzBindAddress string
  178. metricsBindAddress string
  179. }{
  180. {
  181. name: "iptables mode, IPv4 all-zeros bind address",
  182. mode: "iptables",
  183. bindAddress: "0.0.0.0",
  184. clusterCIDR: "1.2.3.0/24",
  185. healthzBindAddress: "1.2.3.4:12345",
  186. metricsBindAddress: "2.3.4.5:23456",
  187. },
  188. {
  189. name: "iptables mode, non-zeros IPv4 config",
  190. mode: "iptables",
  191. bindAddress: "9.8.7.6",
  192. clusterCIDR: "1.2.3.0/24",
  193. healthzBindAddress: "1.2.3.4:12345",
  194. metricsBindAddress: "2.3.4.5:23456",
  195. },
  196. {
  197. // Test for 'bindAddress: "::"' (IPv6 all-zeros) in kube-proxy
  198. // config file. The user will need to put quotes around '::' since
  199. // 'bindAddress: ::' is invalid yaml syntax.
  200. name: "iptables mode, IPv6 \"::\" bind address",
  201. mode: "iptables",
  202. bindAddress: "\"::\"",
  203. clusterCIDR: "fd00:1::0/64",
  204. healthzBindAddress: "[fd00:1::5]:12345",
  205. metricsBindAddress: "[fd00:2::5]:23456",
  206. },
  207. {
  208. // Test for 'bindAddress: "[::]"' (IPv6 all-zeros in brackets)
  209. // in kube-proxy config file. The user will need to use
  210. // surrounding quotes here since 'bindAddress: [::]' is invalid
  211. // yaml syntax.
  212. name: "iptables mode, IPv6 \"[::]\" bind address",
  213. mode: "iptables",
  214. bindAddress: "\"[::]\"",
  215. clusterCIDR: "fd00:1::0/64",
  216. healthzBindAddress: "[fd00:1::5]:12345",
  217. metricsBindAddress: "[fd00:2::5]:23456",
  218. },
  219. {
  220. // Test for 'bindAddress: ::0' (another form of IPv6 all-zeros).
  221. // No surrounding quotes are required around '::0'.
  222. name: "iptables mode, IPv6 ::0 bind address",
  223. mode: "iptables",
  224. bindAddress: "::0",
  225. clusterCIDR: "fd00:1::0/64",
  226. healthzBindAddress: "[fd00:1::5]:12345",
  227. metricsBindAddress: "[fd00:2::5]:23456",
  228. },
  229. {
  230. name: "ipvs mode, IPv6 config",
  231. mode: "ipvs",
  232. bindAddress: "2001:db8::1",
  233. clusterCIDR: "fd00:1::0/64",
  234. healthzBindAddress: "[fd00:1::5]:12345",
  235. metricsBindAddress: "[fd00:2::5]:23456",
  236. },
  237. }
  238. for _, tc := range testCases {
  239. expBindAddr := tc.bindAddress
  240. if tc.bindAddress[0] == '"' {
  241. // Surrounding double quotes will get stripped by the yaml parser.
  242. expBindAddr = expBindAddr[1 : len(tc.bindAddress)-1]
  243. }
  244. expected := &kubeproxyconfig.KubeProxyConfiguration{
  245. BindAddress: expBindAddr,
  246. ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
  247. AcceptContentTypes: "abc",
  248. Burst: 100,
  249. ContentType: "content-type",
  250. Kubeconfig: "/path/to/kubeconfig",
  251. QPS: 7,
  252. },
  253. ClusterCIDR: tc.clusterCIDR,
  254. ConfigSyncPeriod: metav1.Duration{Duration: 15 * time.Second},
  255. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  256. MaxPerCore: utilpointer.Int32Ptr(2),
  257. Min: utilpointer.Int32Ptr(1),
  258. TCPCloseWaitTimeout: &metav1.Duration{Duration: 10 * time.Second},
  259. TCPEstablishedTimeout: &metav1.Duration{Duration: 20 * time.Second},
  260. },
  261. FeatureGates: map[string]bool{},
  262. HealthzBindAddress: tc.healthzBindAddress,
  263. HostnameOverride: "foo",
  264. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  265. MasqueradeAll: true,
  266. MasqueradeBit: utilpointer.Int32Ptr(17),
  267. MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
  268. SyncPeriod: metav1.Duration{Duration: 60 * time.Second},
  269. },
  270. IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{
  271. MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
  272. SyncPeriod: metav1.Duration{Duration: 60 * time.Second},
  273. ExcludeCIDRs: []string{"10.20.30.40/16", "fd00:1::0/64"},
  274. },
  275. MetricsBindAddress: tc.metricsBindAddress,
  276. Mode: kubeproxyconfig.ProxyMode(tc.mode),
  277. OOMScoreAdj: utilpointer.Int32Ptr(17),
  278. PortRange: "2-7",
  279. ResourceContainer: "/foo",
  280. UDPIdleTimeout: metav1.Duration{Duration: 123 * time.Millisecond},
  281. NodePortAddresses: []string{"10.20.30.40/16", "fd00:1::0/64"},
  282. }
  283. options := NewOptions()
  284. yaml := fmt.Sprintf(
  285. yamlTemplate, tc.bindAddress, tc.clusterCIDR,
  286. tc.healthzBindAddress, tc.metricsBindAddress, tc.mode)
  287. config, err := options.loadConfig([]byte(yaml))
  288. assert.NoError(t, err, "unexpected error for %s: %v", tc.name, err)
  289. if !reflect.DeepEqual(expected, config) {
  290. t.Fatalf("unexpected config for %s, diff = %s", tc.name, diff.ObjectDiff(config, expected))
  291. }
  292. }
  293. }
  294. // TestLoadConfigFailures tests failure modes for loadConfig()
  295. func TestLoadConfigFailures(t *testing.T) {
  296. testCases := []struct {
  297. name string
  298. config string
  299. expErr string
  300. }{
  301. {
  302. name: "Decode error test",
  303. config: "Twas bryllyg, and ye slythy toves",
  304. expErr: "could not find expected ':'",
  305. },
  306. {
  307. name: "Bad config type test",
  308. config: "kind: KubeSchedulerConfiguration",
  309. expErr: "no kind",
  310. },
  311. {
  312. name: "Missing quotes around :: bindAddress",
  313. config: "bindAddress: ::",
  314. expErr: "mapping values are not allowed in this context",
  315. },
  316. }
  317. version := "apiVersion: kubeproxy.config.k8s.io/v1alpha1"
  318. for _, tc := range testCases {
  319. options := NewOptions()
  320. config := fmt.Sprintf("%s\n%s", version, tc.config)
  321. _, err := options.loadConfig([]byte(config))
  322. if assert.Error(t, err, tc.name) {
  323. assert.Contains(t, err.Error(), tc.expErr, tc.name)
  324. }
  325. }
  326. }
  327. // TestProcessHostnameOverrideFlag tests processing hostname-override arg
  328. func TestProcessHostnameOverrideFlag(t *testing.T) {
  329. testCases := []struct {
  330. name string
  331. hostnameOverrideFlag string
  332. expectedHostname string
  333. }{
  334. {
  335. name: "Hostname from config file",
  336. hostnameOverrideFlag: "",
  337. expectedHostname: "foo",
  338. },
  339. {
  340. name: "Hostname from flag",
  341. hostnameOverrideFlag: " bar ",
  342. expectedHostname: "bar",
  343. },
  344. }
  345. for _, tc := range testCases {
  346. t.Run(tc.name, func(t *testing.T) {
  347. options := NewOptions()
  348. options.config = &kubeproxyconfig.KubeProxyConfiguration{
  349. HostnameOverride: "foo",
  350. }
  351. options.hostnameOverride = tc.hostnameOverrideFlag
  352. err := options.processHostnameOverrideFlag()
  353. assert.NoError(t, err, "unexpected error %v", err)
  354. if tc.expectedHostname != options.config.HostnameOverride {
  355. t.Fatalf("expected hostname: %s, but got: %s", tc.expectedHostname, options.config.HostnameOverride)
  356. }
  357. })
  358. }
  359. }
  360. func TestConfigChange(t *testing.T) {
  361. setUp := func() (*os.File, string, error) {
  362. tempDir, err := ioutil.TempDir("", "kubeproxy-config-change")
  363. if err != nil {
  364. return nil, "", fmt.Errorf("Unable to create temporary directory: %v", err)
  365. }
  366. fullPath := filepath.Join(tempDir, "kube-proxy-config")
  367. file, err := os.Create(fullPath)
  368. if err != nil {
  369. return nil, "", fmt.Errorf("unexpected error when creating temp file: %v", err)
  370. }
  371. _, err = file.WriteString(`apiVersion: kubeproxy.config.k8s.io/v1alpha1
  372. bindAddress: 0.0.0.0
  373. clientConnection:
  374. acceptContentTypes: ""
  375. burst: 10
  376. contentType: application/vnd.kubernetes.protobuf
  377. kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
  378. qps: 5
  379. clusterCIDR: 10.244.0.0/16
  380. configSyncPeriod: 15m0s
  381. conntrack:
  382. maxPerCore: 32768
  383. min: 131072
  384. tcpCloseWaitTimeout: 1h0m0s
  385. tcpEstablishedTimeout: 24h0m0s
  386. enableProfiling: false
  387. healthzBindAddress: 0.0.0.0:10256
  388. hostnameOverride: ""
  389. iptables:
  390. masqueradeAll: false
  391. masqueradeBit: 14
  392. minSyncPeriod: 0s
  393. syncPeriod: 30s
  394. ipvs:
  395. excludeCIDRs: null
  396. minSyncPeriod: 0s
  397. scheduler: ""
  398. syncPeriod: 30s
  399. kind: KubeProxyConfiguration
  400. metricsBindAddress: 127.0.0.1:10249
  401. mode: ""
  402. nodePortAddresses: null
  403. oomScoreAdj: -999
  404. portRange: ""
  405. resourceContainer: /kube-proxy
  406. udpIdleTimeout: 250ms`)
  407. if err != nil {
  408. return nil, "", fmt.Errorf("unexpected error when writing content to temp kube-proxy config file: %v", err)
  409. }
  410. return file, tempDir, nil
  411. }
  412. tearDown := func(file *os.File, tempDir string) {
  413. file.Close()
  414. os.RemoveAll(tempDir)
  415. }
  416. testCases := []struct {
  417. name string
  418. proxyServer proxyRun
  419. append bool
  420. expectedErr string
  421. }{
  422. {
  423. name: "update config file",
  424. proxyServer: new(fakeProxyServerLongRun),
  425. append: true,
  426. expectedErr: "content of the proxy server's configuration file was updated",
  427. },
  428. {
  429. name: "fake error",
  430. proxyServer: new(fakeProxyServerError),
  431. expectedErr: "mocking error from ProxyServer.Run()",
  432. },
  433. }
  434. for _, tc := range testCases {
  435. file, tempDir, err := setUp()
  436. if err != nil {
  437. t.Fatalf("unexpected error when setting up environment: %v", err)
  438. }
  439. opt := NewOptions()
  440. opt.ConfigFile = file.Name()
  441. err = opt.Complete()
  442. if err != nil {
  443. t.Fatal(err)
  444. }
  445. opt.proxyServer = tc.proxyServer
  446. errCh := make(chan error)
  447. go func() {
  448. errCh <- opt.runLoop()
  449. }()
  450. if tc.append {
  451. file.WriteString("append fake content")
  452. }
  453. select {
  454. case err := <-errCh:
  455. if err != nil {
  456. if !strings.Contains(err.Error(), tc.expectedErr) {
  457. t.Errorf("[%s] Expected error containing %v, got %v", tc.name, tc.expectedErr, err)
  458. }
  459. }
  460. case <-time.After(10 * time.Second):
  461. t.Errorf("[%s] Timeout: unable to get any events or internal timeout.", tc.name)
  462. }
  463. tearDown(file, tempDir)
  464. }
  465. }
  466. type fakeProxyServerLongRun struct{}
  467. // Run runs the specified ProxyServer.
  468. func (s *fakeProxyServerLongRun) Run() error {
  469. for {
  470. time.Sleep(2 * time.Second)
  471. }
  472. }
  473. // CleanupAndExit runs in the specified ProxyServer.
  474. func (s *fakeProxyServerLongRun) CleanupAndExit() error {
  475. return nil
  476. }
  477. type fakeProxyServerError struct{}
  478. // Run runs the specified ProxyServer.
  479. func (s *fakeProxyServerError) Run() error {
  480. for {
  481. time.Sleep(2 * time.Second)
  482. return fmt.Errorf("mocking error from ProxyServer.Run()")
  483. }
  484. }
  485. // CleanupAndExit runs in the specified ProxyServer.
  486. func (s *fakeProxyServerError) CleanupAndExit() error {
  487. return errors.New("mocking error from ProxyServer.CleanupAndExit()")
  488. }
  489. func TestAddressFromDeprecatedFlags(t *testing.T) {
  490. testCases := []struct {
  491. name string
  492. healthzPort int32
  493. healthzBindAddress string
  494. metricsPort int32
  495. metricsBindAddress string
  496. expHealthz string
  497. expMetrics string
  498. }{
  499. {
  500. name: "IPv4 bind address",
  501. healthzBindAddress: "1.2.3.4",
  502. healthzPort: 12345,
  503. metricsBindAddress: "2.3.4.5",
  504. metricsPort: 23456,
  505. expHealthz: "1.2.3.4:12345",
  506. expMetrics: "2.3.4.5:23456",
  507. },
  508. {
  509. name: "IPv4 bind address has port",
  510. healthzBindAddress: "1.2.3.4:12345",
  511. healthzPort: 23456,
  512. metricsBindAddress: "2.3.4.5:12345",
  513. metricsPort: 23456,
  514. expHealthz: "1.2.3.4:12345",
  515. expMetrics: "2.3.4.5:12345",
  516. },
  517. {
  518. name: "IPv6 bind address",
  519. healthzBindAddress: "fd00:1::5",
  520. healthzPort: 12345,
  521. metricsBindAddress: "fd00:1::6",
  522. metricsPort: 23456,
  523. expHealthz: "[fd00:1::5]:12345",
  524. expMetrics: "[fd00:1::6]:23456",
  525. },
  526. {
  527. name: "IPv6 bind address has port",
  528. healthzBindAddress: "[fd00:1::5]:12345",
  529. healthzPort: 56789,
  530. metricsBindAddress: "[fd00:1::6]:56789",
  531. metricsPort: 12345,
  532. expHealthz: "[fd00:1::5]:12345",
  533. expMetrics: "[fd00:1::6]:56789",
  534. },
  535. {
  536. name: "Invalid IPv6 Config",
  537. healthzBindAddress: "[fd00:1::5]",
  538. healthzPort: 12345,
  539. metricsBindAddress: "[fd00:1::6]",
  540. metricsPort: 56789,
  541. expHealthz: "[fd00:1::5]",
  542. expMetrics: "[fd00:1::6]",
  543. },
  544. }
  545. for i := range testCases {
  546. gotHealthz := addressFromDeprecatedFlags(testCases[i].healthzBindAddress, testCases[i].healthzPort)
  547. gotMetrics := addressFromDeprecatedFlags(testCases[i].metricsBindAddress, testCases[i].metricsPort)
  548. errFn := func(name, except, got string) {
  549. t.Errorf("case %s: expected %v, got %v", name, except, got)
  550. }
  551. if gotHealthz != testCases[i].expHealthz {
  552. errFn(testCases[i].name, testCases[i].expHealthz, gotHealthz)
  553. }
  554. if gotMetrics != testCases[i].expMetrics {
  555. errFn(testCases[i].name, testCases[i].expMetrics, gotMetrics)
  556. }
  557. }
  558. }