top_pod_test.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  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 top
  14. import (
  15. "bytes"
  16. "io/ioutil"
  17. "net/http"
  18. "net/url"
  19. "strings"
  20. "testing"
  21. "time"
  22. "github.com/googleapis/gnostic/OpenAPIv2"
  23. "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/api/resource"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/runtime"
  27. apiversion "k8s.io/apimachinery/pkg/version"
  28. "k8s.io/cli-runtime/pkg/genericclioptions"
  29. restclient "k8s.io/client-go/rest"
  30. "k8s.io/client-go/rest/fake"
  31. core "k8s.io/client-go/testing"
  32. cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
  33. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  34. "k8s.io/kubernetes/pkg/kubectl/scheme"
  35. metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
  36. metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
  37. metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake"
  38. )
  39. const (
  40. topPathPrefix = baseMetricsAddress + "/" + metricsAPIVersion
  41. topMetricsAPIPathPrefix = "/apis/metrics.k8s.io/v1beta1"
  42. apibody = `{
  43. "kind": "APIVersions",
  44. "versions": [
  45. "v1"
  46. ],
  47. "serverAddressByClientCIDRs": [
  48. {
  49. "clientCIDR": "0.0.0.0/0",
  50. "serverAddress": "10.0.2.15:8443"
  51. }
  52. ]
  53. }`
  54. // This is not the full output one would usually get, just a trimmed down version.
  55. apisbody = `{
  56. "kind": "APIGroupList",
  57. "apiVersion": "v1",
  58. "groups": [{}]
  59. }`
  60. apisbodyWithMetrics = `{
  61. "kind": "APIGroupList",
  62. "apiVersion": "v1",
  63. "groups": [
  64. {
  65. "name":"metrics.k8s.io",
  66. "versions":[
  67. {
  68. "groupVersion":"metrics.k8s.io/v1beta1",
  69. "version":"v1beta1"
  70. }
  71. ],
  72. "preferredVersion":{
  73. "groupVersion":"metrics.k8s.io/v1beta1",
  74. "version":"v1beta1"
  75. },
  76. "serverAddressByClientCIDRs":null
  77. }
  78. ]
  79. }`
  80. )
  81. func TestTopPod(t *testing.T) {
  82. testNS := "testns"
  83. testCases := []struct {
  84. name string
  85. flags map[string]string
  86. args []string
  87. expectedPath string
  88. expectedQuery string
  89. namespaces []string
  90. containers bool
  91. listsNamespaces bool
  92. }{
  93. {
  94. name: "all namespaces",
  95. flags: map[string]string{"all-namespaces": "true"},
  96. expectedPath: topPathPrefix + "/pods",
  97. namespaces: []string{testNS, "secondtestns", "thirdtestns"},
  98. listsNamespaces: true,
  99. },
  100. {
  101. name: "all in namespace",
  102. expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods",
  103. namespaces: []string{testNS, testNS},
  104. },
  105. {
  106. name: "pod with name",
  107. args: []string{"pod1"},
  108. expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
  109. namespaces: []string{testNS},
  110. },
  111. {
  112. name: "pod with label selector",
  113. flags: map[string]string{"selector": "key=value"},
  114. expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods",
  115. expectedQuery: "labelSelector=" + url.QueryEscape("key=value"),
  116. namespaces: []string{testNS, testNS},
  117. },
  118. {
  119. name: "pod with container metrics",
  120. flags: map[string]string{"containers": "true"},
  121. args: []string{"pod1"},
  122. expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
  123. namespaces: []string{testNS},
  124. containers: true,
  125. },
  126. {
  127. name: "no-headers set",
  128. flags: map[string]string{"containers": "true", "no-headers": "true"},
  129. args: []string{"pod1"},
  130. expectedPath: topPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
  131. namespaces: []string{testNS},
  132. containers: true,
  133. },
  134. }
  135. cmdtesting.InitTestErrorHandler(t)
  136. for _, testCase := range testCases {
  137. t.Run(testCase.name, func(t *testing.T) {
  138. t.Logf("Running test case: %s", testCase.name)
  139. metricsList := testPodMetricsData()
  140. var expectedMetrics []metricsv1alpha1api.PodMetrics
  141. var expectedContainerNames, nonExpectedMetricsNames []string
  142. for n, m := range metricsList {
  143. if n < len(testCase.namespaces) {
  144. m.Namespace = testCase.namespaces[n]
  145. expectedMetrics = append(expectedMetrics, m)
  146. for _, c := range m.Containers {
  147. expectedContainerNames = append(expectedContainerNames, c.Name)
  148. }
  149. } else {
  150. nonExpectedMetricsNames = append(nonExpectedMetricsNames, m.Name)
  151. }
  152. }
  153. var response interface{}
  154. if len(expectedMetrics) == 1 {
  155. response = expectedMetrics[0]
  156. } else {
  157. response = metricsv1alpha1api.PodMetricsList{
  158. ListMeta: metav1.ListMeta{
  159. ResourceVersion: "2",
  160. },
  161. Items: expectedMetrics,
  162. }
  163. }
  164. tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
  165. defer tf.Cleanup()
  166. ns := scheme.Codecs
  167. tf.Client = &fake.RESTClient{
  168. NegotiatedSerializer: ns,
  169. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  170. switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; {
  171. case p == "/api":
  172. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
  173. case p == "/apis":
  174. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
  175. case p == testCase.expectedPath && m == "GET" && (testCase.expectedQuery == "" || q == testCase.expectedQuery):
  176. body, err := marshallBody(response)
  177. if err != nil {
  178. t.Errorf("%s: unexpected error: %v", testCase.name, err)
  179. }
  180. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
  181. default:
  182. t.Fatalf("%s: unexpected request: %#v\nGot URL: %#v\nExpected path: %#v\nExpected query: %#v",
  183. testCase.name, req, req.URL, testCase.expectedPath, testCase.expectedQuery)
  184. return nil, nil
  185. }
  186. }),
  187. }
  188. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  189. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  190. cmd := NewCmdTopPod(tf, nil, streams)
  191. for name, value := range testCase.flags {
  192. cmd.Flags().Set(name, value)
  193. }
  194. cmd.Run(cmd, testCase.args)
  195. // Check the presence of pod names&namespaces/container names in the output.
  196. result := buf.String()
  197. if testCase.containers {
  198. for _, containerName := range expectedContainerNames {
  199. if !strings.Contains(result, containerName) {
  200. t.Errorf("%s: missing metrics for container %s: \n%s", testCase.name, containerName, result)
  201. }
  202. }
  203. }
  204. for _, m := range expectedMetrics {
  205. if !strings.Contains(result, m.Name) {
  206. t.Errorf("%s: missing metrics for %s: \n%s", testCase.name, m.Name, result)
  207. }
  208. if testCase.listsNamespaces && !strings.Contains(result, m.Namespace) {
  209. t.Errorf("%s: missing metrics for %s/%s: \n%s", testCase.name, m.Namespace, m.Name, result)
  210. }
  211. }
  212. for _, name := range nonExpectedMetricsNames {
  213. if strings.Contains(result, name) {
  214. t.Errorf("%s: unexpected metrics for %s: \n%s", testCase.name, name, result)
  215. }
  216. }
  217. if cmdutil.GetFlagBool(cmd, "no-headers") && strings.Contains(result, "MEMORY") {
  218. t.Errorf("%s: unexpected headers with no-headers option set: \n%s", testCase.name, result)
  219. }
  220. })
  221. }
  222. }
  223. func TestTopPodWithMetricsServer(t *testing.T) {
  224. testNS := "testns"
  225. testCases := []struct {
  226. name string
  227. namespace string
  228. options *TopPodOptions
  229. args []string
  230. expectedPath string
  231. expectedQuery string
  232. namespaces []string
  233. containers bool
  234. listsNamespaces bool
  235. }{
  236. {
  237. name: "all namespaces",
  238. options: &TopPodOptions{AllNamespaces: true},
  239. expectedPath: topMetricsAPIPathPrefix + "/pods",
  240. namespaces: []string{testNS, "secondtestns", "thirdtestns"},
  241. listsNamespaces: true,
  242. },
  243. {
  244. name: "all in namespace",
  245. expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods",
  246. namespaces: []string{testNS, testNS},
  247. },
  248. {
  249. name: "pod with name",
  250. args: []string{"pod1"},
  251. expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
  252. namespaces: []string{testNS},
  253. },
  254. {
  255. name: "pod with label selector",
  256. options: &TopPodOptions{Selector: "key=value"},
  257. expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods",
  258. expectedQuery: "labelSelector=" + url.QueryEscape("key=value"),
  259. namespaces: []string{testNS, testNS},
  260. },
  261. {
  262. name: "pod with container metrics",
  263. options: &TopPodOptions{PrintContainers: true},
  264. args: []string{"pod1"},
  265. expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
  266. namespaces: []string{testNS},
  267. containers: true,
  268. },
  269. }
  270. cmdtesting.InitTestErrorHandler(t)
  271. for _, testCase := range testCases {
  272. t.Run(testCase.name, func(t *testing.T) {
  273. metricsList := testV1beta1PodMetricsData()
  274. var expectedMetrics []metricsv1beta1api.PodMetrics
  275. var expectedContainerNames, nonExpectedMetricsNames []string
  276. for n, m := range metricsList {
  277. if n < len(testCase.namespaces) {
  278. m.Namespace = testCase.namespaces[n]
  279. expectedMetrics = append(expectedMetrics, m)
  280. for _, c := range m.Containers {
  281. expectedContainerNames = append(expectedContainerNames, c.Name)
  282. }
  283. } else {
  284. nonExpectedMetricsNames = append(nonExpectedMetricsNames, m.Name)
  285. }
  286. }
  287. fakemetricsClientset := &metricsfake.Clientset{}
  288. if len(expectedMetrics) == 1 {
  289. fakemetricsClientset.AddReactor("get", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  290. return true, &expectedMetrics[0], nil
  291. })
  292. } else {
  293. fakemetricsClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  294. res := &metricsv1beta1api.PodMetricsList{
  295. ListMeta: metav1.ListMeta{
  296. ResourceVersion: "2",
  297. },
  298. Items: expectedMetrics,
  299. }
  300. return true, res, nil
  301. })
  302. }
  303. tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
  304. defer tf.Cleanup()
  305. ns := scheme.Codecs
  306. tf.Client = &fake.RESTClient{
  307. NegotiatedSerializer: ns,
  308. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  309. switch p := req.URL.Path; {
  310. case p == "/api":
  311. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
  312. case p == "/apis":
  313. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
  314. default:
  315. t.Fatalf("%s: unexpected request: %#v\nGot URL: %#v",
  316. testCase.name, req, req.URL)
  317. return nil, nil
  318. }
  319. }),
  320. }
  321. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  322. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  323. cmd := NewCmdTopPod(tf, nil, streams)
  324. var cmdOptions *TopPodOptions
  325. if testCase.options != nil {
  326. cmdOptions = testCase.options
  327. } else {
  328. cmdOptions = &TopPodOptions{}
  329. }
  330. cmdOptions.IOStreams = streams
  331. // TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks
  332. // TODO then check the particular Run functionality and harvest results from fake clients. We probably end up skipping the factory altogether.
  333. if err := cmdOptions.Complete(tf, cmd, testCase.args); err != nil {
  334. t.Fatal(err)
  335. }
  336. cmdOptions.MetricsClient = fakemetricsClientset
  337. if err := cmdOptions.Validate(); err != nil {
  338. t.Fatal(err)
  339. }
  340. if err := cmdOptions.RunTopPod(); err != nil {
  341. t.Fatal(err)
  342. }
  343. // Check the presence of pod names&namespaces/container names in the output.
  344. result := buf.String()
  345. if testCase.containers {
  346. for _, containerName := range expectedContainerNames {
  347. if !strings.Contains(result, containerName) {
  348. t.Errorf("missing metrics for container %s: \n%s", containerName, result)
  349. }
  350. }
  351. }
  352. for _, m := range expectedMetrics {
  353. if !strings.Contains(result, m.Name) {
  354. t.Errorf("missing metrics for %s: \n%s", m.Name, result)
  355. }
  356. if testCase.listsNamespaces && !strings.Contains(result, m.Namespace) {
  357. t.Errorf("missing metrics for %s/%s: \n%s", m.Namespace, m.Name, result)
  358. }
  359. }
  360. for _, name := range nonExpectedMetricsNames {
  361. if strings.Contains(result, name) {
  362. t.Errorf("unexpected metrics for %s: \n%s", name, result)
  363. }
  364. }
  365. })
  366. }
  367. }
  368. type fakeDiscovery struct{}
  369. // ServerGroups returns the supported groups, with information like supported versions and the
  370. // preferred version.
  371. func (d *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
  372. return nil, nil
  373. }
  374. // ServerResourcesForGroupVersion returns the supported resources for a group and version.
  375. func (d *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
  376. return nil, nil
  377. }
  378. // ServerResources returns the supported resources for all groups and versions.
  379. // Deprecated: use ServerGroupsAndResources instead.
  380. func (d *fakeDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
  381. return nil, nil
  382. }
  383. // ServerGroupsAndResources returns the supported groups and resources for all groups and versions.
  384. func (d *fakeDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
  385. return nil, nil, nil
  386. }
  387. // ServerPreferredResources returns the supported resources with the version preferred by the
  388. // server.
  389. func (d *fakeDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
  390. return nil, nil
  391. }
  392. // ServerPreferredNamespacedResources returns the supported namespaced resources with the
  393. // version preferred by the server.
  394. func (d *fakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
  395. return nil, nil
  396. }
  397. // ServerVersion retrieves and parses the server's version (git version).
  398. func (d *fakeDiscovery) ServerVersion() (*apiversion.Info, error) {
  399. return nil, nil
  400. }
  401. // OpenAPISchema retrieves and parses the swagger API schema the server supports.
  402. func (d *fakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
  403. return nil, nil
  404. }
  405. // RESTClient returns a RESTClient that is used to communicate
  406. // with API server by this client implementation.
  407. func (d *fakeDiscovery) RESTClient() restclient.Interface {
  408. return nil
  409. }
  410. func TestTopPodCustomDefaults(t *testing.T) {
  411. customBaseHeapsterServiceAddress := "/api/v1/namespaces/custom-namespace/services/https:custom-heapster-service:/proxy"
  412. customBaseMetricsAddress := customBaseHeapsterServiceAddress + "/apis/metrics"
  413. customTopPathPrefix := customBaseMetricsAddress + "/" + metricsAPIVersion
  414. testNS := "custom-namespace"
  415. testCases := []struct {
  416. name string
  417. flags map[string]string
  418. args []string
  419. expectedPath string
  420. expectedQuery string
  421. namespaces []string
  422. containers bool
  423. listsNamespaces bool
  424. }{
  425. {
  426. name: "all namespaces",
  427. flags: map[string]string{"all-namespaces": "true"},
  428. expectedPath: customTopPathPrefix + "/pods",
  429. namespaces: []string{testNS, "secondtestns", "thirdtestns"},
  430. listsNamespaces: true,
  431. },
  432. {
  433. name: "all in namespace",
  434. expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods",
  435. namespaces: []string{testNS, testNS},
  436. },
  437. {
  438. name: "pod with name",
  439. args: []string{"pod1"},
  440. expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
  441. namespaces: []string{testNS},
  442. },
  443. {
  444. name: "pod with label selector",
  445. flags: map[string]string{"selector": "key=value"},
  446. expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods",
  447. expectedQuery: "labelSelector=" + url.QueryEscape("key=value"),
  448. namespaces: []string{testNS, testNS},
  449. },
  450. {
  451. name: "pod with container metrics",
  452. flags: map[string]string{"containers": "true"},
  453. args: []string{"pod1"},
  454. expectedPath: customTopPathPrefix + "/namespaces/" + testNS + "/pods/pod1",
  455. namespaces: []string{testNS},
  456. containers: true,
  457. },
  458. }
  459. cmdtesting.InitTestErrorHandler(t)
  460. for _, testCase := range testCases {
  461. t.Run(testCase.name, func(t *testing.T) {
  462. t.Logf("Running test case: %s", testCase.name)
  463. metricsList := testPodMetricsData()
  464. var expectedMetrics []metricsv1alpha1api.PodMetrics
  465. var expectedContainerNames, nonExpectedMetricsNames []string
  466. for n, m := range metricsList {
  467. if n < len(testCase.namespaces) {
  468. m.Namespace = testCase.namespaces[n]
  469. expectedMetrics = append(expectedMetrics, m)
  470. for _, c := range m.Containers {
  471. expectedContainerNames = append(expectedContainerNames, c.Name)
  472. }
  473. } else {
  474. nonExpectedMetricsNames = append(nonExpectedMetricsNames, m.Name)
  475. }
  476. }
  477. var response interface{}
  478. if len(expectedMetrics) == 1 {
  479. response = expectedMetrics[0]
  480. } else {
  481. response = metricsv1alpha1api.PodMetricsList{
  482. ListMeta: metav1.ListMeta{
  483. ResourceVersion: "2",
  484. },
  485. Items: expectedMetrics,
  486. }
  487. }
  488. tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
  489. defer tf.Cleanup()
  490. ns := scheme.Codecs
  491. tf.Client = &fake.RESTClient{
  492. NegotiatedSerializer: ns,
  493. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  494. switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; {
  495. case p == "/api":
  496. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
  497. case p == "/apis":
  498. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
  499. case p == testCase.expectedPath && m == "GET" && (testCase.expectedQuery == "" || q == testCase.expectedQuery):
  500. body, err := marshallBody(response)
  501. if err != nil {
  502. t.Errorf("%s: unexpected error: %v", testCase.name, err)
  503. }
  504. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
  505. default:
  506. t.Fatalf("%s: unexpected request: %#v\nGot URL: %#v\nExpected path: %#v\nExpected query: %#v",
  507. testCase.name, req, req.URL, testCase.expectedPath, testCase.expectedQuery)
  508. return nil, nil
  509. }
  510. }),
  511. }
  512. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  513. streams, _, buf, _ := genericclioptions.NewTestIOStreams()
  514. opts := &TopPodOptions{
  515. HeapsterOptions: HeapsterTopOptions{
  516. Namespace: "custom-namespace",
  517. Scheme: "https",
  518. Service: "custom-heapster-service",
  519. },
  520. DiscoveryClient: &fakeDiscovery{},
  521. IOStreams: streams,
  522. }
  523. cmd := NewCmdTopPod(tf, opts, streams)
  524. for name, value := range testCase.flags {
  525. cmd.Flags().Set(name, value)
  526. }
  527. cmd.Run(cmd, testCase.args)
  528. // Check the presence of pod names&namespaces/container names in the output.
  529. result := buf.String()
  530. if testCase.containers {
  531. for _, containerName := range expectedContainerNames {
  532. if !strings.Contains(result, containerName) {
  533. t.Errorf("%s: missing metrics for container %s: \n%s", testCase.name, containerName, result)
  534. }
  535. }
  536. }
  537. for _, m := range expectedMetrics {
  538. if !strings.Contains(result, m.Name) {
  539. t.Errorf("%s: missing metrics for %s: \n%s", testCase.name, m.Name, result)
  540. }
  541. if testCase.listsNamespaces && !strings.Contains(result, m.Namespace) {
  542. t.Errorf("%s: missing metrics for %s/%s: \n%s", testCase.name, m.Namespace, m.Name, result)
  543. }
  544. }
  545. for _, name := range nonExpectedMetricsNames {
  546. if strings.Contains(result, name) {
  547. t.Errorf("%s: unexpected metrics for %s: \n%s", testCase.name, name, result)
  548. }
  549. }
  550. })
  551. }
  552. }
  553. func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
  554. return []metricsv1beta1api.PodMetrics{
  555. {
  556. ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10", Labels: map[string]string{"key": "value"}},
  557. Window: metav1.Duration{Duration: time.Minute},
  558. Containers: []metricsv1beta1api.ContainerMetrics{
  559. {
  560. Name: "container1-1",
  561. Usage: v1.ResourceList{
  562. v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
  563. v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
  564. v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
  565. },
  566. },
  567. {
  568. Name: "container1-2",
  569. Usage: v1.ResourceList{
  570. v1.ResourceCPU: *resource.NewMilliQuantity(4, resource.DecimalSI),
  571. v1.ResourceMemory: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI),
  572. v1.ResourceStorage: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
  573. },
  574. },
  575. },
  576. },
  577. {
  578. ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: "test", ResourceVersion: "11", Labels: map[string]string{"key": "value"}},
  579. Window: metav1.Duration{Duration: time.Minute},
  580. Containers: []metricsv1beta1api.ContainerMetrics{
  581. {
  582. Name: "container2-1",
  583. Usage: v1.ResourceList{
  584. v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
  585. v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
  586. v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
  587. },
  588. },
  589. {
  590. Name: "container2-2",
  591. Usage: v1.ResourceList{
  592. v1.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI),
  593. v1.ResourceMemory: *resource.NewQuantity(11*(1024*1024), resource.DecimalSI),
  594. v1.ResourceStorage: *resource.NewQuantity(12*(1024*1024), resource.DecimalSI),
  595. },
  596. },
  597. {
  598. Name: "container2-3",
  599. Usage: v1.ResourceList{
  600. v1.ResourceCPU: *resource.NewMilliQuantity(13, resource.DecimalSI),
  601. v1.ResourceMemory: *resource.NewQuantity(14*(1024*1024), resource.DecimalSI),
  602. v1.ResourceStorage: *resource.NewQuantity(15*(1024*1024), resource.DecimalSI),
  603. },
  604. },
  605. },
  606. },
  607. {
  608. ObjectMeta: metav1.ObjectMeta{Name: "pod3", Namespace: "test", ResourceVersion: "12"},
  609. Window: metav1.Duration{Duration: time.Minute},
  610. Containers: []metricsv1beta1api.ContainerMetrics{
  611. {
  612. Name: "container3-1",
  613. Usage: v1.ResourceList{
  614. v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
  615. v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
  616. v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
  617. },
  618. },
  619. },
  620. },
  621. }
  622. }
  623. func testPodMetricsData() []metricsv1alpha1api.PodMetrics {
  624. return []metricsv1alpha1api.PodMetrics{
  625. {
  626. ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "test", ResourceVersion: "10"},
  627. Window: metav1.Duration{Duration: time.Minute},
  628. Containers: []metricsv1alpha1api.ContainerMetrics{
  629. {
  630. Name: "container1-1",
  631. Usage: v1.ResourceList{
  632. v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
  633. v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
  634. v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
  635. },
  636. },
  637. {
  638. Name: "container1-2",
  639. Usage: v1.ResourceList{
  640. v1.ResourceCPU: *resource.NewMilliQuantity(4, resource.DecimalSI),
  641. v1.ResourceMemory: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI),
  642. v1.ResourceStorage: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
  643. },
  644. },
  645. },
  646. },
  647. {
  648. ObjectMeta: metav1.ObjectMeta{Name: "pod2", Namespace: "test", ResourceVersion: "11"},
  649. Window: metav1.Duration{Duration: time.Minute},
  650. Containers: []metricsv1alpha1api.ContainerMetrics{
  651. {
  652. Name: "container2-1",
  653. Usage: v1.ResourceList{
  654. v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
  655. v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
  656. v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
  657. },
  658. },
  659. {
  660. Name: "container2-2",
  661. Usage: v1.ResourceList{
  662. v1.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI),
  663. v1.ResourceMemory: *resource.NewQuantity(11*(1024*1024), resource.DecimalSI),
  664. v1.ResourceStorage: *resource.NewQuantity(12*(1024*1024), resource.DecimalSI),
  665. },
  666. },
  667. {
  668. Name: "container2-3",
  669. Usage: v1.ResourceList{
  670. v1.ResourceCPU: *resource.NewMilliQuantity(13, resource.DecimalSI),
  671. v1.ResourceMemory: *resource.NewQuantity(14*(1024*1024), resource.DecimalSI),
  672. v1.ResourceStorage: *resource.NewQuantity(15*(1024*1024), resource.DecimalSI),
  673. },
  674. },
  675. },
  676. },
  677. {
  678. ObjectMeta: metav1.ObjectMeta{Name: "pod3", Namespace: "test", ResourceVersion: "12"},
  679. Window: metav1.Duration{Duration: time.Minute},
  680. Containers: []metricsv1alpha1api.ContainerMetrics{
  681. {
  682. Name: "container3-1",
  683. Usage: v1.ResourceList{
  684. v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
  685. v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
  686. v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
  687. },
  688. },
  689. },
  690. },
  691. }
  692. }