prober_test.go 11 KB

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