prober_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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 prober
  14. import (
  15. "bytes"
  16. "errors"
  17. "fmt"
  18. "net/http"
  19. "reflect"
  20. "strings"
  21. "testing"
  22. "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/util/intstr"
  24. "k8s.io/client-go/tools/record"
  25. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  26. containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
  27. "k8s.io/kubernetes/pkg/kubelet/prober/results"
  28. "k8s.io/kubernetes/pkg/kubelet/util/ioutils"
  29. "k8s.io/kubernetes/pkg/probe"
  30. execprobe "k8s.io/kubernetes/pkg/probe/exec"
  31. )
  32. func TestFormatURL(t *testing.T) {
  33. testCases := []struct {
  34. scheme string
  35. host string
  36. port int
  37. path string
  38. result string
  39. }{
  40. {"http", "localhost", 93, "", "http://localhost:93"},
  41. {"https", "localhost", 93, "/path", "https://localhost:93/path"},
  42. {"http", "localhost", 93, "?foo", "http://localhost:93?foo"},
  43. {"https", "localhost", 93, "/path?bar", "https://localhost:93/path?bar"},
  44. }
  45. for _, test := range testCases {
  46. url := formatURL(test.scheme, test.host, test.port, test.path)
  47. if url.String() != test.result {
  48. t.Errorf("Expected %s, got %s", test.result, url.String())
  49. }
  50. }
  51. }
  52. func TestFindPortByName(t *testing.T) {
  53. container := v1.Container{
  54. Ports: []v1.ContainerPort{
  55. {
  56. Name: "foo",
  57. ContainerPort: 8080,
  58. },
  59. {
  60. Name: "bar",
  61. ContainerPort: 9000,
  62. },
  63. },
  64. }
  65. want := 8080
  66. got, err := findPortByName(container, "foo")
  67. if got != want || err != nil {
  68. t.Errorf("Expected %v, got %v, err: %v", want, got, err)
  69. }
  70. }
  71. func TestGetURLParts(t *testing.T) {
  72. testCases := []struct {
  73. probe *v1.HTTPGetAction
  74. ok bool
  75. host string
  76. port int
  77. path string
  78. }{
  79. {&v1.HTTPGetAction{Host: "", Port: intstr.FromInt(-1), Path: ""}, false, "", -1, ""},
  80. {&v1.HTTPGetAction{Host: "", Port: intstr.FromString(""), Path: ""}, false, "", -1, ""},
  81. {&v1.HTTPGetAction{Host: "", Port: intstr.FromString("-1"), Path: ""}, false, "", -1, ""},
  82. {&v1.HTTPGetAction{Host: "", Port: intstr.FromString("not-found"), Path: ""}, false, "", -1, ""},
  83. {&v1.HTTPGetAction{Host: "", Port: intstr.FromString("found"), Path: ""}, true, "127.0.0.1", 93, ""},
  84. {&v1.HTTPGetAction{Host: "", Port: intstr.FromInt(76), Path: ""}, true, "127.0.0.1", 76, ""},
  85. {&v1.HTTPGetAction{Host: "", Port: intstr.FromString("118"), Path: ""}, true, "127.0.0.1", 118, ""},
  86. {&v1.HTTPGetAction{Host: "hostname", Port: intstr.FromInt(76), Path: "path"}, true, "hostname", 76, "path"},
  87. }
  88. for _, test := range testCases {
  89. state := v1.PodStatus{PodIP: "127.0.0.1"}
  90. container := v1.Container{
  91. Ports: []v1.ContainerPort{{Name: "found", ContainerPort: 93}},
  92. LivenessProbe: &v1.Probe{
  93. Handler: v1.Handler{
  94. HTTPGet: test.probe,
  95. },
  96. },
  97. }
  98. scheme := test.probe.Scheme
  99. if scheme == "" {
  100. scheme = v1.URISchemeHTTP
  101. }
  102. host := test.probe.Host
  103. if host == "" {
  104. host = state.PodIP
  105. }
  106. port, err := extractPort(test.probe.Port, container)
  107. if test.ok && err != nil {
  108. t.Errorf("Unexpected error: %v", err)
  109. }
  110. path := test.probe.Path
  111. if !test.ok && err == nil {
  112. t.Errorf("Expected error for %+v, got %s%s:%d/%s", test, scheme, host, port, path)
  113. }
  114. if test.ok {
  115. if host != test.host || port != test.port || path != test.path {
  116. t.Errorf("Expected %s:%d/%s, got %s:%d/%s",
  117. test.host, test.port, test.path, host, port, path)
  118. }
  119. }
  120. }
  121. }
  122. func TestGetTCPAddrParts(t *testing.T) {
  123. testCases := []struct {
  124. probe *v1.TCPSocketAction
  125. ok bool
  126. host string
  127. port int
  128. }{
  129. {&v1.TCPSocketAction{Port: intstr.FromInt(-1)}, false, "", -1},
  130. {&v1.TCPSocketAction{Port: intstr.FromString("")}, false, "", -1},
  131. {&v1.TCPSocketAction{Port: intstr.FromString("-1")}, false, "", -1},
  132. {&v1.TCPSocketAction{Port: intstr.FromString("not-found")}, false, "", -1},
  133. {&v1.TCPSocketAction{Port: intstr.FromString("found")}, true, "1.2.3.4", 93},
  134. {&v1.TCPSocketAction{Port: intstr.FromInt(76)}, true, "1.2.3.4", 76},
  135. {&v1.TCPSocketAction{Port: intstr.FromString("118")}, true, "1.2.3.4", 118},
  136. }
  137. for _, test := range testCases {
  138. host := "1.2.3.4"
  139. container := v1.Container{
  140. Ports: []v1.ContainerPort{{Name: "found", ContainerPort: 93}},
  141. LivenessProbe: &v1.Probe{
  142. Handler: v1.Handler{
  143. TCPSocket: test.probe,
  144. },
  145. },
  146. }
  147. port, err := extractPort(test.probe.Port, container)
  148. if !test.ok && err == nil {
  149. t.Errorf("Expected error for %+v, got %s:%d", test, host, port)
  150. }
  151. if test.ok && err != nil {
  152. t.Errorf("Unexpected error: %v", err)
  153. }
  154. if test.ok {
  155. if host != test.host || port != test.port {
  156. t.Errorf("Expected %s:%d, got %s:%d", test.host, test.port, host, port)
  157. }
  158. }
  159. }
  160. }
  161. func TestHTTPHeaders(t *testing.T) {
  162. testCases := []struct {
  163. input []v1.HTTPHeader
  164. output http.Header
  165. }{
  166. {[]v1.HTTPHeader{}, http.Header{}},
  167. {[]v1.HTTPHeader{
  168. {Name: "X-Muffins-Or-Cupcakes", Value: "Muffins"},
  169. }, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins"}}},
  170. {[]v1.HTTPHeader{
  171. {Name: "X-Muffins-Or-Cupcakes", Value: "Muffins"},
  172. {Name: "X-Muffins-Or-Plumcakes", Value: "Muffins!"},
  173. }, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins"},
  174. "X-Muffins-Or-Plumcakes": {"Muffins!"}}},
  175. {[]v1.HTTPHeader{
  176. {Name: "X-Muffins-Or-Cupcakes", Value: "Muffins"},
  177. {Name: "X-Muffins-Or-Cupcakes", Value: "Cupcakes, too"},
  178. }, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins", "Cupcakes, too"}}},
  179. }
  180. for _, test := range testCases {
  181. headers := buildHeader(test.input)
  182. if !reflect.DeepEqual(test.output, headers) {
  183. t.Errorf("Expected %#v, got %#v", test.output, headers)
  184. }
  185. }
  186. }
  187. func TestProbe(t *testing.T) {
  188. containerID := kubecontainer.ContainerID{Type: "test", ID: "foobar"}
  189. execProbe := &v1.Probe{
  190. Handler: v1.Handler{
  191. Exec: &v1.ExecAction{},
  192. },
  193. }
  194. tests := []struct {
  195. probe *v1.Probe
  196. env []v1.EnvVar
  197. execError bool
  198. expectError bool
  199. execResult probe.Result
  200. expectedResult results.Result
  201. expectCommand []string
  202. }{
  203. { // No probe
  204. probe: nil,
  205. expectedResult: results.Success,
  206. },
  207. { // No handler
  208. probe: &v1.Probe{},
  209. expectError: true,
  210. expectedResult: results.Failure,
  211. },
  212. { // Probe fails
  213. probe: execProbe,
  214. execResult: probe.Failure,
  215. expectedResult: results.Failure,
  216. },
  217. { // Probe succeeds
  218. probe: execProbe,
  219. execResult: probe.Success,
  220. expectedResult: results.Success,
  221. },
  222. { // Probe result is warning
  223. probe: execProbe,
  224. execResult: probe.Warning,
  225. expectedResult: results.Success,
  226. },
  227. { // Probe result is unknown
  228. probe: execProbe,
  229. execResult: probe.Unknown,
  230. expectedResult: results.Failure,
  231. },
  232. { // Probe has an error
  233. probe: execProbe,
  234. execError: true,
  235. expectError: true,
  236. execResult: probe.Unknown,
  237. expectedResult: results.Failure,
  238. },
  239. { // Probe arguments are passed through
  240. probe: &v1.Probe{
  241. Handler: v1.Handler{
  242. Exec: &v1.ExecAction{
  243. Command: []string{"/bin/bash", "-c", "some script"},
  244. },
  245. },
  246. },
  247. expectCommand: []string{"/bin/bash", "-c", "some script"},
  248. execResult: probe.Success,
  249. expectedResult: results.Success,
  250. },
  251. { // Probe arguments are passed through
  252. probe: &v1.Probe{
  253. Handler: v1.Handler{
  254. Exec: &v1.ExecAction{
  255. Command: []string{"/bin/bash", "-c", "some $(A) $(B)"},
  256. },
  257. },
  258. },
  259. env: []v1.EnvVar{
  260. {Name: "A", Value: "script"},
  261. },
  262. expectCommand: []string{"/bin/bash", "-c", "some script $(B)"},
  263. execResult: probe.Success,
  264. expectedResult: results.Success,
  265. },
  266. }
  267. for i, test := range tests {
  268. for _, probeType := range [...]probeType{liveness, readiness, startup} {
  269. prober := &prober{
  270. refManager: kubecontainer.NewRefManager(),
  271. recorder: &record.FakeRecorder{},
  272. }
  273. testID := fmt.Sprintf("%d-%s", i, probeType)
  274. testContainer := v1.Container{Env: test.env}
  275. switch probeType {
  276. case liveness:
  277. testContainer.LivenessProbe = test.probe
  278. case readiness:
  279. testContainer.ReadinessProbe = test.probe
  280. case startup:
  281. testContainer.StartupProbe = test.probe
  282. }
  283. if test.execError {
  284. prober.exec = fakeExecProber{test.execResult, errors.New("exec error")}
  285. } else {
  286. prober.exec = fakeExecProber{test.execResult, nil}
  287. }
  288. result, err := prober.probe(probeType, &v1.Pod{}, v1.PodStatus{}, testContainer, containerID)
  289. if test.expectError && err == nil {
  290. t.Errorf("[%s] Expected probe error but no error was returned.", testID)
  291. }
  292. if !test.expectError && err != nil {
  293. t.Errorf("[%s] Didn't expect probe error but got: %v", testID, err)
  294. }
  295. if test.expectedResult != result {
  296. t.Errorf("[%s] Expected result to be %v but was %v", testID, test.expectedResult, result)
  297. }
  298. if len(test.expectCommand) > 0 {
  299. prober.exec = execprobe.New()
  300. prober.runner = &containertest.FakeContainerCommandRunner{}
  301. _, err := prober.probe(probeType, &v1.Pod{}, v1.PodStatus{}, testContainer, containerID)
  302. if err != nil {
  303. t.Errorf("[%s] Didn't expect probe error but got: %v", testID, err)
  304. continue
  305. }
  306. if !reflect.DeepEqual(test.expectCommand, prober.runner.(*containertest.FakeContainerCommandRunner).Cmd) {
  307. t.Errorf("[%s] unexpected probe arguments: %v", testID, prober.runner.(*containertest.FakeContainerCommandRunner).Cmd)
  308. }
  309. }
  310. }
  311. }
  312. }
  313. func TestNewExecInContainer(t *testing.T) {
  314. limit := 1024
  315. tenKilobyte := strings.Repeat("logs-123", 128*10)
  316. tests := []struct {
  317. name string
  318. stdout string
  319. expected string
  320. err error
  321. }{
  322. {
  323. name: "no error",
  324. stdout: "foo",
  325. expected: "foo",
  326. err: nil,
  327. },
  328. {
  329. name: "no error",
  330. stdout: tenKilobyte,
  331. expected: tenKilobyte[0:limit],
  332. err: nil,
  333. },
  334. {
  335. name: "error - make sure we get output",
  336. stdout: "foo",
  337. expected: "foo",
  338. err: errors.New("bad"),
  339. },
  340. }
  341. for _, test := range tests {
  342. runner := &containertest.FakeContainerCommandRunner{
  343. Stdout: test.stdout,
  344. Err: test.err,
  345. }
  346. prober := &prober{
  347. runner: runner,
  348. }
  349. container := v1.Container{}
  350. containerID := kubecontainer.ContainerID{Type: "docker", ID: "containerID"}
  351. cmd := []string{"/foo", "bar"}
  352. exec := prober.newExecInContainer(container, containerID, cmd, 0)
  353. var dataBuffer bytes.Buffer
  354. writer := ioutils.LimitWriter(&dataBuffer, int64(limit))
  355. exec.SetStderr(writer)
  356. exec.SetStdout(writer)
  357. err := exec.Start()
  358. if err == nil {
  359. err = exec.Wait()
  360. }
  361. actualOutput := dataBuffer.Bytes()
  362. if e, a := containerID, runner.ContainerID; e != a {
  363. t.Errorf("%s: container id: expected %v, got %v", test.name, e, a)
  364. }
  365. if e, a := cmd, runner.Cmd; !reflect.DeepEqual(e, a) {
  366. t.Errorf("%s: cmd: expected %v, got %v", test.name, e, a)
  367. }
  368. // this isn't 100% foolproof as a bug in a real ContainerCommandRunner where it fails to copy to stdout/stderr wouldn't be caught by this test
  369. if e, a := test.expected, string(actualOutput); e != a {
  370. t.Errorf("%s: output: expected %q, got %q", test.name, e, a)
  371. }
  372. if e, a := fmt.Sprintf("%v", test.err), fmt.Sprintf("%v", err); e != a {
  373. t.Errorf("%s: error: expected %s, got %s", test.name, e, a)
  374. }
  375. }
  376. }