kubelet_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. /*
  2. Copyright 2016 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 e2e_node
  14. import (
  15. "bytes"
  16. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "net/http"
  20. "strings"
  21. "time"
  22. "k8s.io/kubernetes/pkg/api"
  23. apiUnversioned "k8s.io/kubernetes/pkg/api/unversioned"
  24. "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats"
  25. "k8s.io/kubernetes/pkg/util/sets"
  26. "k8s.io/kubernetes/pkg/util/uuid"
  27. "k8s.io/kubernetes/test/e2e/framework"
  28. "github.com/davecgh/go-spew/spew"
  29. . "github.com/onsi/ginkgo"
  30. . "github.com/onsi/gomega"
  31. )
  32. var _ = framework.KubeDescribe("Kubelet", func() {
  33. f := framework.NewDefaultFramework("kubelet-test")
  34. var podClient *framework.PodClient
  35. BeforeEach(func() {
  36. podClient = f.PodClient()
  37. })
  38. Context("when scheduling a busybox command in a pod", func() {
  39. podName := "busybox-scheduling-" + string(uuid.NewUUID())
  40. It("it should print the output to logs", func() {
  41. podClient.CreateSync(&api.Pod{
  42. ObjectMeta: api.ObjectMeta{
  43. Name: podName,
  44. },
  45. Spec: api.PodSpec{
  46. // Don't restart the Pod since it is expected to exit
  47. RestartPolicy: api.RestartPolicyNever,
  48. Containers: []api.Container{
  49. {
  50. Image: ImageRegistry[busyBoxImage],
  51. Name: podName,
  52. Command: []string{"sh", "-c", "echo 'Hello World' ; sleep 240"},
  53. },
  54. },
  55. },
  56. })
  57. Eventually(func() string {
  58. sinceTime := apiUnversioned.NewTime(time.Now().Add(time.Duration(-1 * time.Hour)))
  59. rc, err := podClient.GetLogs(podName, &api.PodLogOptions{SinceTime: &sinceTime}).Stream()
  60. if err != nil {
  61. return ""
  62. }
  63. defer rc.Close()
  64. buf := new(bytes.Buffer)
  65. buf.ReadFrom(rc)
  66. return buf.String()
  67. }, time.Minute, time.Second*4).Should(Equal("Hello World\n"))
  68. })
  69. })
  70. Context("when scheduling a read only busybox container", func() {
  71. podName := "busybox-readonly-fs" + string(uuid.NewUUID())
  72. It("it should not write to root filesystem", func() {
  73. isReadOnly := true
  74. podClient.CreateSync(&api.Pod{
  75. ObjectMeta: api.ObjectMeta{
  76. Name: podName,
  77. },
  78. Spec: api.PodSpec{
  79. // Don't restart the Pod since it is expected to exit
  80. RestartPolicy: api.RestartPolicyNever,
  81. Containers: []api.Container{
  82. {
  83. Image: ImageRegistry[busyBoxImage],
  84. Name: podName,
  85. Command: []string{"sh", "-c", "echo test > /file; sleep 240"},
  86. SecurityContext: &api.SecurityContext{
  87. ReadOnlyRootFilesystem: &isReadOnly,
  88. },
  89. },
  90. },
  91. },
  92. })
  93. Eventually(func() string {
  94. rc, err := podClient.GetLogs(podName, &api.PodLogOptions{}).Stream()
  95. if err != nil {
  96. return ""
  97. }
  98. defer rc.Close()
  99. buf := new(bytes.Buffer)
  100. buf.ReadFrom(rc)
  101. return buf.String()
  102. }, time.Minute, time.Second*4).Should(Equal("sh: can't create /file: Read-only file system\n"))
  103. })
  104. })
  105. Describe("metrics api", func() {
  106. Context("when querying /stats/summary", func() {
  107. It("it should report resource usage through the stats api", func() {
  108. podNamePrefix := "stats-busybox-" + string(uuid.NewUUID())
  109. volumeNamePrefix := "test-empty-dir"
  110. podNames, volumes := createSummaryTestPods(f.PodClient(), podNamePrefix, 2, volumeNamePrefix)
  111. By("Returning stats summary")
  112. summary := stats.Summary{}
  113. Eventually(func() error {
  114. resp, err := http.Get(*kubeletAddress + "/stats/summary")
  115. if err != nil {
  116. return fmt.Errorf("Failed to get /stats/summary - %v", err)
  117. }
  118. contentsBytes, err := ioutil.ReadAll(resp.Body)
  119. if err != nil {
  120. return fmt.Errorf("Failed to read /stats/summary - %+v", resp)
  121. }
  122. contents := string(contentsBytes)
  123. decoder := json.NewDecoder(strings.NewReader(contents))
  124. err = decoder.Decode(&summary)
  125. if err != nil {
  126. return fmt.Errorf("Failed to parse /stats/summary to go struct: %+v", resp)
  127. }
  128. missingPods := podsMissingFromSummary(summary, podNames)
  129. if missingPods.Len() != 0 {
  130. return fmt.Errorf("expected pods not found. Following pods are missing - %v", missingPods)
  131. }
  132. missingVolumes := volumesMissingFromSummary(summary, volumes)
  133. if missingVolumes.Len() != 0 {
  134. return fmt.Errorf("expected volumes not found. Following volumes are missing - %v", missingVolumes)
  135. }
  136. if err := testSummaryMetrics(summary, podNamePrefix); err != nil {
  137. return err
  138. }
  139. return nil
  140. }, 5*time.Minute, time.Second*4).Should(BeNil())
  141. })
  142. })
  143. })
  144. })
  145. const (
  146. containerSuffix = "-c"
  147. )
  148. func createSummaryTestPods(podClient *framework.PodClient, podNamePrefix string, count int, volumeNamePrefix string) (sets.String, sets.String) {
  149. podNames := sets.NewString()
  150. volumes := sets.NewString(volumeNamePrefix)
  151. for i := 0; i < count; i++ {
  152. podNames.Insert(fmt.Sprintf("%s%v", podNamePrefix, i))
  153. }
  154. var pods []*api.Pod
  155. for _, podName := range podNames.List() {
  156. pods = append(pods, &api.Pod{
  157. ObjectMeta: api.ObjectMeta{
  158. Name: podName,
  159. },
  160. Spec: api.PodSpec{
  161. // Don't restart the Pod since it is expected to exit
  162. RestartPolicy: api.RestartPolicyNever,
  163. Containers: []api.Container{
  164. {
  165. Image: ImageRegistry[busyBoxImage],
  166. Command: []string{"sh", "-c", "while true; do echo 'hello world' | tee /test-empty-dir-mnt/file ; sleep 1; done"},
  167. Name: podName + containerSuffix,
  168. VolumeMounts: []api.VolumeMount{
  169. {MountPath: "/test-empty-dir-mnt", Name: volumeNamePrefix},
  170. },
  171. },
  172. },
  173. SecurityContext: &api.PodSecurityContext{
  174. SELinuxOptions: &api.SELinuxOptions{
  175. Level: "s0",
  176. },
  177. },
  178. Volumes: []api.Volume{
  179. // TODO: Test secret volumes
  180. // TODO: Test hostpath volumes
  181. {Name: volumeNamePrefix, VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
  182. },
  183. },
  184. })
  185. }
  186. podClient.CreateBatch(pods)
  187. return podNames, volumes
  188. }
  189. // Returns pods missing from summary.
  190. func podsMissingFromSummary(s stats.Summary, expectedPods sets.String) sets.String {
  191. expectedPods = sets.StringKeySet(expectedPods)
  192. for _, pod := range s.Pods {
  193. if expectedPods.Has(pod.PodRef.Name) {
  194. expectedPods.Delete(pod.PodRef.Name)
  195. }
  196. }
  197. return expectedPods
  198. }
  199. // Returns volumes missing from summary.
  200. func volumesMissingFromSummary(s stats.Summary, expectedVolumes sets.String) sets.String {
  201. for _, pod := range s.Pods {
  202. expectedPodVolumes := sets.StringKeySet(expectedVolumes)
  203. for _, vs := range pod.VolumeStats {
  204. if expectedPodVolumes.Has(vs.Name) {
  205. expectedPodVolumes.Delete(vs.Name)
  206. }
  207. }
  208. if expectedPodVolumes.Len() != 0 {
  209. return expectedPodVolumes
  210. }
  211. }
  212. return sets.NewString()
  213. }
  214. func testSummaryMetrics(s stats.Summary, podNamePrefix string) error {
  215. const (
  216. nonNilValue = "expected %q to not be nil"
  217. nonZeroValue = "expected %q to not be zero"
  218. )
  219. if s.Node.NodeName != framework.TestContext.NodeName {
  220. return fmt.Errorf("unexpected node name - %q", s.Node.NodeName)
  221. }
  222. if s.Node.CPU.UsageCoreNanoSeconds == nil {
  223. return fmt.Errorf(nonNilValue, "cpu instantaneous")
  224. }
  225. if *s.Node.CPU.UsageCoreNanoSeconds == 0 {
  226. return fmt.Errorf(nonZeroValue, "cpu instantaneous")
  227. }
  228. if s.Node.Memory.UsageBytes == nil {
  229. return fmt.Errorf(nonNilValue, "memory")
  230. }
  231. if *s.Node.Memory.UsageBytes == 0 {
  232. return fmt.Errorf(nonZeroValue, "memory")
  233. }
  234. if s.Node.Memory.WorkingSetBytes == nil {
  235. return fmt.Errorf(nonNilValue, "memory working set")
  236. }
  237. if *s.Node.Memory.WorkingSetBytes == 0 {
  238. return fmt.Errorf(nonZeroValue, "memory working set")
  239. }
  240. if s.Node.Fs.AvailableBytes == nil {
  241. return fmt.Errorf(nonNilValue, "memory working set")
  242. }
  243. if *s.Node.Fs.AvailableBytes == 0 {
  244. return fmt.Errorf(nonZeroValue, "node Fs available")
  245. }
  246. if s.Node.Fs.CapacityBytes == nil {
  247. return fmt.Errorf(nonNilValue, "node fs capacity")
  248. }
  249. if *s.Node.Fs.CapacityBytes == 0 {
  250. return fmt.Errorf(nonZeroValue, "node fs capacity")
  251. }
  252. if s.Node.Fs.UsedBytes == nil {
  253. return fmt.Errorf(nonNilValue, "node fs used")
  254. }
  255. if *s.Node.Fs.UsedBytes == 0 {
  256. return fmt.Errorf(nonZeroValue, "node fs used")
  257. }
  258. if s.Node.Runtime == nil {
  259. return fmt.Errorf(nonNilValue, "node runtime")
  260. }
  261. if s.Node.Runtime.ImageFs == nil {
  262. return fmt.Errorf(nonNilValue, "runtime image Fs")
  263. }
  264. if s.Node.Runtime.ImageFs.AvailableBytes == nil {
  265. return fmt.Errorf(nonNilValue, "runtime image Fs available")
  266. }
  267. if *s.Node.Runtime.ImageFs.AvailableBytes == 0 {
  268. return fmt.Errorf(nonZeroValue, "runtime image Fs available")
  269. }
  270. if s.Node.Runtime.ImageFs.CapacityBytes == nil {
  271. return fmt.Errorf(nonNilValue, "runtime image Fs capacity")
  272. }
  273. if *s.Node.Runtime.ImageFs.CapacityBytes == 0 {
  274. return fmt.Errorf(nonZeroValue, "runtime image Fs capacity")
  275. }
  276. if s.Node.Runtime.ImageFs.UsedBytes == nil {
  277. return fmt.Errorf(nonNilValue, "runtime image Fs usage")
  278. }
  279. if *s.Node.Runtime.ImageFs.UsedBytes == 0 {
  280. return fmt.Errorf(nonZeroValue, "runtime image Fs usage")
  281. }
  282. sysContainers := map[string]stats.ContainerStats{}
  283. for _, container := range s.Node.SystemContainers {
  284. sysContainers[container.Name] = container
  285. if err := expectContainerStatsNotEmpty(&container); err != nil {
  286. return err
  287. }
  288. }
  289. if _, exists := sysContainers["kubelet"]; !exists {
  290. return fmt.Errorf("expected metrics for kubelet")
  291. }
  292. if _, exists := sysContainers["runtime"]; !exists {
  293. return fmt.Errorf("expected metrics for runtime")
  294. }
  295. // Verify Pods Stats are present
  296. podsList := []string{}
  297. By("Having resources for pods")
  298. for _, pod := range s.Pods {
  299. if !strings.HasPrefix(pod.PodRef.Name, podNamePrefix) {
  300. // Ignore pods created outside this test
  301. continue
  302. }
  303. podsList = append(podsList, pod.PodRef.Name)
  304. if len(pod.Containers) != 1 {
  305. return fmt.Errorf("expected only one container")
  306. }
  307. container := pod.Containers[0]
  308. if container.Name != (pod.PodRef.Name + containerSuffix) {
  309. return fmt.Errorf("unexpected container name - %q", container.Name)
  310. }
  311. if err := expectContainerStatsNotEmpty(&container); err != nil {
  312. return err
  313. }
  314. // emptydir volume
  315. foundExpectedVolume := false
  316. for _, vs := range pod.VolumeStats {
  317. if *vs.CapacityBytes == 0 {
  318. return fmt.Errorf(nonZeroValue, "volume capacity")
  319. }
  320. if *vs.AvailableBytes == 0 {
  321. return fmt.Errorf(nonZeroValue, "volume available")
  322. }
  323. if *vs.UsedBytes == 0 {
  324. return fmt.Errorf(nonZeroValue, "volume used")
  325. }
  326. if vs.Name == "test-empty-dir" {
  327. foundExpectedVolume = true
  328. }
  329. }
  330. if !foundExpectedVolume {
  331. return fmt.Errorf("expected 'test-empty-dir' volume")
  332. }
  333. // fs usage (not for system containers)
  334. if container.Rootfs == nil {
  335. return fmt.Errorf(nonNilValue+" - "+spew.Sdump(container), "container root fs")
  336. }
  337. if container.Rootfs.AvailableBytes == nil {
  338. return fmt.Errorf(nonNilValue+" - "+spew.Sdump(container), "container root fs available")
  339. }
  340. if *container.Rootfs.AvailableBytes == 0 {
  341. return fmt.Errorf(nonZeroValue+" - "+spew.Sdump(container), "container root fs available")
  342. }
  343. if container.Rootfs.CapacityBytes == nil {
  344. return fmt.Errorf(nonNilValue+" - "+spew.Sdump(container), "container root fs capacity")
  345. }
  346. if *container.Rootfs.CapacityBytes == 0 {
  347. return fmt.Errorf(nonZeroValue+" - "+spew.Sdump(container), "container root fs capacity")
  348. }
  349. if container.Rootfs.UsedBytes == nil {
  350. return fmt.Errorf(nonNilValue+" - "+spew.Sdump(container), "container root fs usage")
  351. }
  352. if *container.Rootfs.UsedBytes == 0 {
  353. return fmt.Errorf(nonZeroValue+" - "+spew.Sdump(container), "container root fs usage")
  354. }
  355. if container.Logs == nil {
  356. return fmt.Errorf(nonNilValue+" - "+spew.Sdump(container), "container logs")
  357. }
  358. if container.Logs.AvailableBytes == nil {
  359. return fmt.Errorf(nonNilValue+" - "+spew.Sdump(container), "container logs available")
  360. }
  361. if *container.Logs.AvailableBytes == 0 {
  362. return fmt.Errorf(nonZeroValue+" - "+spew.Sdump(container), "container logs available")
  363. }
  364. if container.Logs.CapacityBytes == nil {
  365. return fmt.Errorf(nonNilValue+" - "+spew.Sdump(container), "container logs capacity")
  366. }
  367. if *container.Logs.CapacityBytes == 0 {
  368. return fmt.Errorf(nonZeroValue+" - "+spew.Sdump(container), "container logs capacity")
  369. }
  370. if container.Logs.UsedBytes == nil {
  371. return fmt.Errorf(nonNilValue+" - "+spew.Sdump(container), "container logs usage")
  372. }
  373. if *container.Logs.UsedBytes == 0 {
  374. return fmt.Errorf(nonZeroValue+" - "+spew.Sdump(container), "container logs usage")
  375. }
  376. }
  377. return nil
  378. }
  379. func expectContainerStatsNotEmpty(container *stats.ContainerStats) error {
  380. // TODO: Test Network
  381. if container.CPU == nil {
  382. return fmt.Errorf("expected container cpu to be not nil - %q", spew.Sdump(container))
  383. }
  384. if container.CPU.UsageCoreNanoSeconds == nil {
  385. return fmt.Errorf("expected container cpu instantaneous usage to be not nil - %q", spew.Sdump(container))
  386. }
  387. if *container.CPU.UsageCoreNanoSeconds == 0 {
  388. return fmt.Errorf("expected container cpu instantaneous usage to be non zero - %q", spew.Sdump(container))
  389. }
  390. if container.Memory == nil {
  391. return fmt.Errorf("expected container memory to be not nil - %q", spew.Sdump(container))
  392. }
  393. if container.Memory.UsageBytes == nil {
  394. return fmt.Errorf("expected container memory usage to be not nil - %q", spew.Sdump(container))
  395. }
  396. if *container.Memory.UsageBytes == 0 {
  397. return fmt.Errorf("expected container memory usage to be non zero - %q", spew.Sdump(container))
  398. }
  399. if container.Memory.WorkingSetBytes == nil {
  400. return fmt.Errorf("expected container memory working set to be not nil - %q", spew.Sdump(container))
  401. }
  402. if *container.Memory.WorkingSetBytes == 0 {
  403. return fmt.Errorf("expected container memory working set to be non zero - %q", spew.Sdump(container))
  404. }
  405. return nil
  406. }