From 2d29ae52a8000e1ab761eb35b85d646e613d7a6a Mon Sep 17 00:00:00 2001 From: digitalfishpond Date: Fri, 13 May 2016 10:55:21 +0200 Subject: [PATCH] Add pod details page --- src/app/backend/handler/apihandler.go | 18 +++ src/app/backend/resource/pod/podcommon.go | 74 +++++++++ src/app/backend/resource/pod/poddetail.go | 73 +++++++++ src/app/backend/resource/pod/podlist.go | 27 +--- src/app/externs/backendapi.js | 17 ++- src/app/frontend/chrome/chrome.scss | 4 + src/app/frontend/index_module.js | 2 + src/app/frontend/poddetail/poddetail.html | 25 +++ .../poddetail/poddetail_controller.js | 27 ++++ .../frontend/poddetail/poddetail_module.js | 38 +++++ src/app/frontend/poddetail/poddetail_state.js | 36 +++++ .../poddetail/poddetail_stateconfig.js | 68 +++++++++ src/app/frontend/poddetail/podinfo.html | 57 +++++++ .../frontend/poddetail/podinfo_component.js | 43 ++++++ src/app/frontend/podlist/podcardlist.html | 6 +- .../frontend/podlist/podcardlist_component.js | 19 ++- .../replicasetlist/replicasetcard.html | 2 +- .../backend/resource/pod/podcommon_test.go | 144 ++++++++++++++++++ src/test/backend/resource/pod/podlist_test.go | 69 --------- .../poddetail/poddetail_controller_test.js | 29 ++++ .../poddetail/podinfo_component_test.js | 32 ++++ .../podcardlist_component_test.js | 13 +- .../podlist_controller_test.js | 4 +- .../podlist_stateconfig_test.js | 0 .../podlistactionbar_component_test.js | 4 +- 25 files changed, 725 insertions(+), 106 deletions(-) create mode 100644 src/app/backend/resource/pod/podcommon.go create mode 100644 src/app/backend/resource/pod/poddetail.go create mode 100644 src/app/frontend/poddetail/poddetail.html create mode 100644 src/app/frontend/poddetail/poddetail_controller.js create mode 100644 src/app/frontend/poddetail/poddetail_module.js create mode 100644 src/app/frontend/poddetail/poddetail_state.js create mode 100644 src/app/frontend/poddetail/poddetail_stateconfig.js create mode 100644 src/app/frontend/poddetail/podinfo.html create mode 100644 src/app/frontend/poddetail/podinfo_component.js create mode 100644 src/test/backend/resource/pod/podcommon_test.go delete mode 100644 src/test/backend/resource/pod/podlist_test.go create mode 100644 src/test/frontend/poddetail/poddetail_controller_test.js create mode 100644 src/test/frontend/poddetail/podinfo_component_test.js rename src/test/frontend/{podslist => podlist}/podcardlist_component_test.js (76%) rename src/test/frontend/{podslist => podlist}/podlist_controller_test.js (87%) rename src/test/frontend/{podslist => podlist}/podlist_stateconfig_test.js (100%) rename src/test/frontend/{podslist => podlist}/podlistactionbar_component_test.js (93%) diff --git a/src/app/backend/handler/apihandler.go b/src/app/backend/handler/apihandler.go index 2124dac95d37..04b19378b2c2 100644 --- a/src/app/backend/handler/apihandler.go +++ b/src/app/backend/handler/apihandler.go @@ -191,6 +191,10 @@ func CreateHttpApiHandler(client *client.Client, heapsterClient HeapsterClient, podsWs.GET(""). To(apiHandler.handleGetPods). Writes(pod.PodList{})) + podsWs.Route( + podsWs.GET("/{namespace}/{pod}"). + To(apiHandler.handleGetPodDetail). + Writes(pod.PodDetail{})) wsContainer.Add(podsWs) deploymentsWs := new(restful.WebService) @@ -481,6 +485,20 @@ func (apiHandler *ApiHandler) handleGetPods( response.WriteHeaderAndEntity(http.StatusCreated, result) } +// Handles get Pod detail API call. +func (apiHandler *ApiHandler) handleGetPodDetail(request *restful.Request, response *restful.Response) { + + namespace := request.PathParameter("namespace") + podName := request.PathParameter("pod") + result, err := pod.GetPodDetail(apiHandler.client, apiHandler.heapsterClient, namespace, podName) + if err != nil { + handleInternalError(response, err) + return + } + + response.WriteHeaderAndEntity(http.StatusCreated, result) +} + // Handles get Replication Controller detail API call. func (apiHandler *ApiHandler) handleGetReplicationControllerDetail( request *restful.Request, response *restful.Response) { diff --git a/src/app/backend/resource/pod/podcommon.go b/src/app/backend/resource/pod/podcommon.go new file mode 100644 index 000000000000..3cfae003e84f --- /dev/null +++ b/src/app/backend/resource/pod/podcommon.go @@ -0,0 +1,74 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pod + +import ( + "github.com/kubernetes/dashboard/resource/common" + "k8s.io/kubernetes/pkg/api" +) + +// Gets restart count of given pod (total number of its containers restarts). +func getRestartCount(pod api.Pod) int { + restartCount := 0 + for _, containerStatus := range pod.Status.ContainerStatuses { + restartCount += containerStatus.RestartCount + } + return restartCount +} + +func ToPod(pod *api.Pod, metrics *MetricsByPod) Pod { + podDetail := Pod{ + ObjectMeta: common.NewObjectMeta(pod.ObjectMeta), + TypeMeta: common.NewTypeMeta(common.ResourceKindPod), + PodPhase: pod.Status.Phase, + PodIP: pod.Status.PodIP, + RestartCount: getRestartCount(*pod), + } + + if metrics != nil && metrics.MetricsMap[pod.Namespace] != nil { + metric := metrics.MetricsMap[pod.Namespace][pod.Name] + podDetail.Metrics = &metric + } + + return podDetail +} + +func ToPodDetail(pod *api.Pod, metrics *MetricsByPod) PodDetail { + podDetail := PodDetail{ + ObjectMeta: common.NewObjectMeta(pod.ObjectMeta), + TypeMeta: common.NewTypeMeta(common.ResourceKindPod), + PodPhase: pod.Status.Phase, + PodIP: pod.Status.PodIP, + RestartCount: getRestartCount(*pod), + ContainerImages: GetContainerImages(&pod.Spec), + NodeName: pod.Spec.NodeName, + } + + if metrics != nil && metrics.MetricsMap[pod.Namespace] != nil { + metric := metrics.MetricsMap[pod.Namespace][pod.Name] + podDetail.Metrics = &metric + } + + return podDetail +} + +// GetContainerImages returns container image strings from the given pod spec. +func GetContainerImages(podTemplate *api.PodSpec) []string { + var containerImages []string + for _, container := range podTemplate.Containers { + containerImages = append(containerImages, container.Image) + } + return containerImages +} diff --git a/src/app/backend/resource/pod/poddetail.go b/src/app/backend/resource/pod/poddetail.go new file mode 100644 index 000000000000..51b4a0ac4370 --- /dev/null +++ b/src/app/backend/resource/pod/poddetail.go @@ -0,0 +1,73 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pod + +import ( + "log" + + "k8s.io/kubernetes/pkg/api" + k8sClient "k8s.io/kubernetes/pkg/client/unversioned" + + "github.com/kubernetes/dashboard/client" + "github.com/kubernetes/dashboard/resource/common" +) + +// PodDetail is a presentation layer view of Kubernetes PodDetail resource. +// This means it is PodDetail plus additional augumented data we can get +// from other sources (like services that target it). +type PodDetail struct { + ObjectMeta common.ObjectMeta `json:"objectMeta"` + TypeMeta common.TypeMeta `json:"typeMeta"` + + // Container images of the Pod. + ContainerImages []string `json:"containerImages"` + + // Status of the Pod. See Kubernetes API for reference. + PodPhase api.PodPhase `json:"podPhase"` + + // IP address of the Pod. + PodIP string `json:"podIP"` + + // Name of the Node this Pod runs on. + NodeName string `json:"nodeName"` + + // Count of containers restarts. + RestartCount int `json:"restartCount"` + + // Pod metrics. + Metrics *PodMetrics `json:"metrics"` +} + +// GetPodDetail returns the details (PodDetail) of a named Pod from a particular +// namespace. +func GetPodDetail(client k8sClient.Interface, heapsterClient client.HeapsterClient, + namespace, name string) (*PodDetail, error) { + + log.Printf("Getting details of %s pod in %s namespace", name, namespace) + + // TODO(floreks): Use channels. + pod, err := client.Pods(namespace).Get(name) + if err != nil { + return nil, err + } + + metrics, err := getPodMetrics([]api.Pod{*pod}, heapsterClient) + if err != nil { + log.Printf("Skipping Heapster metrics because of error: %s\n", err) + } + + podDetail := ToPodDetail(pod, metrics) + return &podDetail, nil +}; diff --git a/src/app/backend/resource/pod/podlist.go b/src/app/backend/resource/pod/podlist.go index b5f5a0faeb69..a775c40e1249 100644 --- a/src/app/backend/resource/pod/podlist.go +++ b/src/app/backend/resource/pod/podlist.go @@ -37,18 +37,12 @@ type Pod struct { ObjectMeta common.ObjectMeta `json:"objectMeta"` TypeMeta common.TypeMeta `json:"typeMeta"` - // Container images of the Pod. - ContainerImages []string `json:"containerImages"` - // Status of the Pod. See Kubernetes API for reference. PodPhase api.PodPhase `json:"podPhase"` // IP address of the Pod. PodIP string `json:"podIP"` - // Name of the Node this Pod runs on. - NodeName string `json:"nodeName"` - // Count of containers restarts. RestartCount int `json:"restartCount"` @@ -92,28 +86,9 @@ func CreatePodList(pods []api.Pod, heapsterClient client.HeapsterClient) PodList } for _, pod := range pods { - podDetail := Pod{ - ObjectMeta: common.NewObjectMeta(pod.ObjectMeta), - TypeMeta: common.NewTypeMeta(common.ResourceKindPod), - PodPhase: pod.Status.Phase, - PodIP: pod.Status.PodIP, - RestartCount: getRestartCount(pod), - } - if metrics != nil && metrics.MetricsMap[pod.Namespace] != nil { - metric := metrics.MetricsMap[pod.Namespace][pod.Name] - podDetail.Metrics = &metric - } + podDetail := ToPod(&pod, metrics) podList.Pods = append(podList.Pods, podDetail) } return podList } - -// Gets restart count of given pod (total number of its containers restarts). -func getRestartCount(pod api.Pod) int { - restartCount := 0 - for _, containerStatus := range pod.Status.ContainerStatuses { - restartCount += containerStatus.RestartCount - } - return restartCount -} diff --git a/src/app/externs/backendapi.js b/src/app/externs/backendapi.js index 2819ba8894e9..1afd9dca1897 100644 --- a/src/app/externs/backendapi.js +++ b/src/app/externs/backendapi.js @@ -269,15 +269,28 @@ backendApi.DeleteReplicationControllerSpec; * @typedef {{ * objectMeta: !backendApi.ObjectMeta, * typeMeta: !backendApi.TypeMeta, - * status: string, + * podPhase: string, * podIP: string, - * nodeName: string, * restartCount: number, * metrics: backendApi.PodMetrics * }} */ backendApi.Pod; +/** + * @typedef {{ + * objectMeta: !backendApi.ObjectMeta, + * typeMeta: !backendApi.TypeMeta, + * containerImages: !Array, + * podPhase: string, + * podIP: string, + * nodeName: string, + * restartCount: number, + * metrics: backendApi.PodMetrics + * }} + */ +backendApi.PodDetail; + /** * @typedef {{ * objectMeta: !backendApi.ObjectMeta, diff --git a/src/app/frontend/chrome/chrome.scss b/src/app/frontend/chrome/chrome.scss index edf0a082cd19..a1889bf69b1a 100644 --- a/src/app/frontend/chrome/chrome.scss +++ b/src/app/frontend/chrome/chrome.scss @@ -20,6 +20,10 @@ width: 42px; } +.kd-middle-ellipsised-link { + display: block; +} + .kd-toolbar { box-shadow: $whiteframe-shadow-1dp; height: $toolbar-height-size-base; diff --git a/src/app/frontend/index_module.js b/src/app/frontend/index_module.js index 933e0eda7082..bf311ccefb68 100644 --- a/src/app/frontend/index_module.js +++ b/src/app/frontend/index_module.js @@ -30,6 +30,7 @@ import routeConfig from './index_route'; import serviceDetailModule from './servicedetail/servicedetail_module'; import serviceListModule from './servicelist/servicelist_module'; import workloadsModule from './workloads/workloads_module'; +import podDetailModule from './poddetail/poddetail_module'; export default angular .module( @@ -54,6 +55,7 @@ export default angular workloadsModule.name, serviceDetailModule.name, serviceListModule.name, + podDetailModule.name, ]) .config(indexConfig) .config(routeConfig); diff --git a/src/app/frontend/poddetail/poddetail.html b/src/app/frontend/poddetail/poddetail.html new file mode 100644 index 000000000000..007150537838 --- /dev/null +++ b/src/app/frontend/poddetail/poddetail.html @@ -0,0 +1,25 @@ + + +
+ + + + + + + +
diff --git a/src/app/frontend/poddetail/poddetail_controller.js b/src/app/frontend/poddetail/poddetail_controller.js new file mode 100644 index 000000000000..4b8ef03c6e6b --- /dev/null +++ b/src/app/frontend/poddetail/poddetail_controller.js @@ -0,0 +1,27 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @final + */ +export class PodDetailController { + /** + * @ngInject + * @param {!backendApi.PodDetail} podDetail + */ + constructor(podDetail) { + /** @export {!backendApi.PodDetail} */ + this.podDetail = podDetail; + } +} diff --git a/src/app/frontend/poddetail/poddetail_module.js b/src/app/frontend/poddetail/poddetail_module.js new file mode 100644 index 000000000000..adf338ffbd65 --- /dev/null +++ b/src/app/frontend/poddetail/poddetail_module.js @@ -0,0 +1,38 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import componentsModule from 'common/components/components_module'; +import eventsModule from 'events/events_module'; +import filtersModule from 'common/filters/filters_module'; +import stateConfig from './poddetail_stateconfig'; +import {podInfoComponent} from './podinfo_component'; + +/** + * Angular module for the Replica Set details view. + * + * The view shows detailed view of a Replica Set. + */ +export default angular + .module( + 'kubernetesDashboard.podDetail', + [ + 'ngMaterial', + 'ngResource', + 'ui.router', + componentsModule.name, + filtersModule.name, + eventsModule.name, + ]) + .config(stateConfig) + .component('kdPodInfo', podInfoComponent); diff --git a/src/app/frontend/poddetail/poddetail_state.js b/src/app/frontend/poddetail/poddetail_state.js new file mode 100644 index 000000000000..9968fe7224ef --- /dev/null +++ b/src/app/frontend/poddetail/poddetail_state.js @@ -0,0 +1,36 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** Name of the state. Can be used in, e.g., $state.go method. */ +export const stateName = 'poddetail'; + +/** + * Parameters for this state. + * + * All properties are @exported and in sync with URL param names. + * @final + */ +export class StateParams { + /** + * @param {string} namespace + * @param {string} pod + */ + constructor(namespace, pod) { + /** @export {string} Namespace of this Pod. */ + this.namespace = namespace; + + /** @export {string} Name of this Pod. */ + this.pod = pod; + } +} diff --git a/src/app/frontend/poddetail/poddetail_stateconfig.js b/src/app/frontend/poddetail/poddetail_stateconfig.js new file mode 100644 index 000000000000..80f087220c64 --- /dev/null +++ b/src/app/frontend/poddetail/poddetail_stateconfig.js @@ -0,0 +1,68 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {actionbarViewName} from 'chrome/chrome_state'; +import {breadcrumbsConfig} from 'common/components/breadcrumbs/breadcrumbs_component'; +import {PodDetailController} from './poddetail_controller'; +import {stateName as podList, stateUrl} from 'podlist/podlist_state'; +import {stateName} from './poddetail_state'; + +/** + * Configures states for the pod details view. + * + * @param {!ui.router.$stateProvider} $stateProvider + * @ngInject + */ +export default function stateConfig($stateProvider) { + $stateProvider.state(stateName, { + url: `${stateUrl}/:namespace/:pod`, + resolve: { + 'podDetailResource': getPodDetailResource, + 'podDetail': getPodDetail, + }, + data: { + [breadcrumbsConfig]: { + 'label': '{{$stateParams.pod}}', + 'parent': podList, + }, + }, + views: { + '': { + controller: PodDetailController, + controllerAs: 'ctrl', + templateUrl: 'poddetail/poddetail.html', + }, + [actionbarViewName]: {}, + }, + }); +} + +/** + * @param {!./poddetail_state.StateParams} $stateParams + * @param {!angular.$resource} $resource + * @return {!angular.Resource} + * @ngInject + */ +export function getPodDetailResource($resource, $stateParams) { + return $resource(`api/v1/pods/${$stateParams.namespace}/${$stateParams.pod}`); +} + +/** + * @param {!angular.Resource} podDetailResource + * @return {!angular.$q.Promise} + * @ngInject + */ +export function getPodDetail(podDetailResource) { + return podDetailResource.get().$promise; +} diff --git a/src/app/frontend/poddetail/podinfo.html b/src/app/frontend/poddetail/podinfo.html new file mode 100644 index 000000000000..a41c1c9978f4 --- /dev/null +++ b/src/app/frontend/poddetail/podinfo.html @@ -0,0 +1,57 @@ + + + + Resource details + + + + + + + {{::$ctrl.pod.objectMeta.namespace}} + + + {{::$ctrl.pod.objectMeta.creationTimestamp | date:'d/M/yy HH:mm':'UTC'}} + + +
+ +
+
+ none +
+
+ + {{::$ctrl.pod.podPhase}} + + +
+ +
+
+
+ + + + {{::$ctrl.pod.nodeName}} + + + {{::$ctrl.pod.podIP}} + + +
diff --git a/src/app/frontend/poddetail/podinfo_component.js b/src/app/frontend/poddetail/podinfo_component.js new file mode 100644 index 000000000000..4233f7287f54 --- /dev/null +++ b/src/app/frontend/poddetail/podinfo_component.js @@ -0,0 +1,43 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @final + */ +export default class PodInfoController { + /** + * Constructs pod info object. + */ + constructor() { + /** + * Pod details. Initialized from the scope. + * @export {!backendApi.PodDetail} + */ + this.pod; + } +} + +/** + * Definition object for the component that displays pod info. + * + * @return {!angular.Directive} + */ +export const podInfoComponent = { + controller: PodInfoController, + templateUrl: 'poddetail/podinfo.html', + bindings: { + /** {!backendApi.PodDetail} */ + 'pod': '<', + }, +}; diff --git a/src/app/frontend/podlist/podcardlist.html b/src/app/frontend/podlist/podcardlist.html index a1e634687b5e..554d5a5703a7 100644 --- a/src/app/frontend/podlist/podcardlist.html +++ b/src/app/frontend/podlist/podcardlist.html @@ -34,8 +34,10 @@
- - + + + +
{{::pod.podPhase}} diff --git a/src/app/frontend/podlist/podcardlist_component.js b/src/app/frontend/podlist/podcardlist_component.js index 6fcca9ba9c12..590c94e63c7b 100644 --- a/src/app/frontend/podlist/podcardlist_component.js +++ b/src/app/frontend/podlist/podcardlist_component.js @@ -12,14 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +import {StateParams} from 'poddetail/poddetail_state'; +import {stateName} from 'poddetail/poddetail_state'; + /** * @final */ export class PodCardListController { /** * @ngInject + * @param {!ui.router.$state} $state */ - constructor() { + constructor($state) { /** * List of pods. Initialized from the scope. * @export {!backendApi.PodList} @@ -31,6 +35,9 @@ export class PodCardListController { * @export {!function({pod: !backendApi.Pod}): string} */ this.logsHrefFn; + + /** @private {!ui.router.$state} */ + this.state_ = $state; } /** @@ -39,6 +46,16 @@ export class PodCardListController { * @export */ getPodLogsHref(pod) { return this.logsHrefFn({pod: pod}); } + + /** + * @param {!backendApi.Pod} pod + * @return {string} + * @export + */ + getPodDetailHref(pod) { + return this.state_.href( + stateName, new StateParams(pod.objectMeta.namespace, pod.objectMeta.name)); + } } /** diff --git a/src/app/frontend/replicasetlist/replicasetcard.html b/src/app/frontend/replicasetlist/replicasetcard.html index a1a25f7bfd48..d60fe0805018 100644 --- a/src/app/frontend/replicasetlist/replicasetcard.html +++ b/src/app/frontend/replicasetlist/replicasetcard.html @@ -35,7 +35,7 @@
- + diff --git a/src/test/backend/resource/pod/podcommon_test.go b/src/test/backend/resource/pod/podcommon_test.go new file mode 100644 index 000000000000..54c9e494f95b --- /dev/null +++ b/src/test/backend/resource/pod/podcommon_test.go @@ -0,0 +1,144 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:Service//www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pod + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + + "github.com/kubernetes/dashboard/resource/common" +) + +func TestGetRestartCount(t *testing.T) { + cases := []struct { + pod api.Pod + expected int + }{ + { + api.Pod{}, 0, + }, + { + api.Pod{ + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "container-1", + RestartCount: 1, + }, + }, + }, + }, + 1, + }, + { + api.Pod{ + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: "container-1", + RestartCount: 3, + }, + { + Name: "container-2", + RestartCount: 2, + }, + }, + }, + }, + 5, + }, + } + for _, c := range cases { + actual := getRestartCount(c.pod) + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("AppendEvents(%#v) == %#v, expected %#v", c.pod, actual, c.expected) + } + } +} + +func TestToPodDetail(t *testing.T) { + cases := []struct { + pod *api.Pod + metrics *MetricsByPod + expected PodDetail + }{ + { + pod: &api.Pod{}, metrics: &MetricsByPod{}, expected: PodDetail{ + TypeMeta: common.TypeMeta{Kind: common.ResourceKindPod}, + }, + + }, { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "test-pod", Namespace: "test-namespace", + }}, + metrics: &MetricsByPod{}, + expected: PodDetail{ + TypeMeta: common.TypeMeta{Kind: common.ResourceKindPod}, + ObjectMeta: common.ObjectMeta{ + Name: "test-pod", + Namespace: "test-namespace", + }, + }, + }, + } + + for _, c := range cases { + actual := ToPodDetail(c.pod, c.metrics) + + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("ToPodDetail(%#v) == \ngot %#v, \nexpected %#v", c.pod, actual, + c.expected) + } + } +} + +func TestToPod(t *testing.T) { + cases := []struct { + pod *api.Pod + metrics *MetricsByPod + expected Pod + }{ + { + pod: &api.Pod{}, metrics: &MetricsByPod{}, expected: Pod{ + TypeMeta: common.TypeMeta{Kind: common.ResourceKindPod}, + }, + }, { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "test-pod", Namespace: "test-namespace", + }}, + metrics: &MetricsByPod{}, + expected: Pod{ + TypeMeta: common.TypeMeta{Kind: common.ResourceKindPod}, + ObjectMeta: common.ObjectMeta{ + Name: "test-pod", + Namespace: "test-namespace", + }, + }, + }, + } + + for _, c := range cases { + actual := ToPod(c.pod, c.metrics) + + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("ToPod(%#v) == \ngot %#v, \nexpected %#v", c.pod, actual, + c.expected) + } + } +} diff --git a/src/test/backend/resource/pod/podlist_test.go b/src/test/backend/resource/pod/podlist_test.go deleted file mode 100644 index 68bda102aa1c..000000000000 --- a/src/test/backend/resource/pod/podlist_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pod - -import ( - "reflect" - "testing" - - "k8s.io/kubernetes/pkg/api" -) - -func TestGetRestartCount(t *testing.T) { - cases := []struct { - pod api.Pod - expected int - }{ - { - api.Pod{}, 0, - }, - { - api.Pod{ - Status: api.PodStatus{ - ContainerStatuses: []api.ContainerStatus{ - { - Name: "container-1", - RestartCount: 1, - }, - }, - }, - }, - 1, - }, - { - api.Pod{ - Status: api.PodStatus{ - ContainerStatuses: []api.ContainerStatus{ - { - Name: "container-1", - RestartCount: 3, - }, - { - Name: "container-2", - RestartCount: 2, - }, - }, - }, - }, - 5, - }, - } - for _, c := range cases { - actual := getRestartCount(c.pod) - if !reflect.DeepEqual(actual, c.expected) { - t.Errorf("AppendEvents(%#v) == %#v, expected %#v", c.pod, actual, c.expected) - } - } -} diff --git a/src/test/frontend/poddetail/poddetail_controller_test.js b/src/test/frontend/poddetail/poddetail_controller_test.js new file mode 100644 index 000000000000..f406168b0ea7 --- /dev/null +++ b/src/test/frontend/poddetail/poddetail_controller_test.js @@ -0,0 +1,29 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {PodDetailController} from 'poddetail/poddetail_controller'; +import podDetailModule from 'poddetail/poddetail_module'; + +describe('Pod detail controller', () => { + + beforeEach(() => { angular.mock.module(podDetailModule.name); }); + + it('should initialize controller', angular.mock.inject(($controller) => { + let data = {podDetail: {}}; + /** @type {!PodDetailController} */ + let ctrl = $controller(PodDetailController, {podDetail: data}); + + expect(ctrl.podDetail).toBe(data); + })); +}); diff --git a/src/test/frontend/poddetail/podinfo_component_test.js b/src/test/frontend/poddetail/podinfo_component_test.js new file mode 100644 index 000000000000..c201ce155a92 --- /dev/null +++ b/src/test/frontend/poddetail/podinfo_component_test.js @@ -0,0 +1,32 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import podDetailModule from 'poddetail/poddetail_module'; + +describe('Pod Info controller', () => { + /** + * Pod Info controller. + * @type {!PodInfoController} + */ + let ctrl; + + beforeEach(() => { + angular.mock.module(podDetailModule.name); + + angular.mock.inject( + ($componentController) => { ctrl = $componentController('kdPodInfo', {}); }); + }); + + it('should instantiate the controller properly', () => { expect(ctrl).not.toBeUndefined(); }); +}); diff --git a/src/test/frontend/podslist/podcardlist_component_test.js b/src/test/frontend/podlist/podcardlist_component_test.js similarity index 76% rename from src/test/frontend/podslist/podcardlist_component_test.js rename to src/test/frontend/podlist/podcardlist_component_test.js index f3a39478b094..e7a4fef218ee 100644 --- a/src/test/frontend/podslist/podcardlist_component_test.js +++ b/src/test/frontend/podlist/podcardlist_component_test.js @@ -13,8 +13,9 @@ // limitations under the License. import podsListModule from 'podlist/podlist_module'; +import podDetailModule from 'poddetail/poddetail_module'; -describe('Pods list controller', () => { +describe('Pod card list controller', () => { /** * @type {!podlist/podcardlist_component.PodCardListController} */ @@ -22,6 +23,7 @@ describe('Pods list controller', () => { beforeEach(() => { angular.mock.module(podsListModule.name); + angular.mock.module(podDetailModule.name); angular.mock.inject(($componentController) => { ctrl = $componentController('kdPodCardList', {}, { @@ -41,4 +43,13 @@ describe('Pods list controller', () => { // then expect(ctrl.logsHrefFn).toHaveBeenCalledWith({pod: pod}); }); + + it('should execute logs href callback function', () => { + expect(ctrl.getPodDetailHref({ + objectMeta: { + name: 'foo-pod', + namespace: 'foo-namespace', + }, + })).toBe('#/pods/foo-namespace/foo-pod'); + }); }); diff --git a/src/test/frontend/podslist/podlist_controller_test.js b/src/test/frontend/podlist/podlist_controller_test.js similarity index 87% rename from src/test/frontend/podslist/podlist_controller_test.js rename to src/test/frontend/podlist/podlist_controller_test.js index 30dc81d94113..45213b4730ec 100644 --- a/src/test/frontend/podslist/podlist_controller_test.js +++ b/src/test/frontend/podlist/podlist_controller_test.js @@ -15,11 +15,11 @@ import {PodListController} from 'podlist/podlist_controller'; import podListModule from 'podlist/podlist_module'; -describe('Replica Set list controller', () => { +describe('Pod list controller', () => { beforeEach(() => { angular.mock.module(podListModule.name); }); - it('should initialize replication controllers', angular.mock.inject(($controller) => { + it('should initialize pod list', angular.mock.inject(($controller) => { let data = {pods: {}}; /** @type {!PodListController} */ let ctrl = $controller(PodListController, {podList: data}); diff --git a/src/test/frontend/podslist/podlist_stateconfig_test.js b/src/test/frontend/podlist/podlist_stateconfig_test.js similarity index 100% rename from src/test/frontend/podslist/podlist_stateconfig_test.js rename to src/test/frontend/podlist/podlist_stateconfig_test.js diff --git a/src/test/frontend/podslist/podlistactionbar_component_test.js b/src/test/frontend/podlist/podlistactionbar_component_test.js similarity index 93% rename from src/test/frontend/podslist/podlistactionbar_component_test.js rename to src/test/frontend/podlist/podlistactionbar_component_test.js index da356aeddb5a..abde83d04bdb 100644 --- a/src/test/frontend/podslist/podlistactionbar_component_test.js +++ b/src/test/frontend/podlist/podlistactionbar_component_test.js @@ -16,9 +16,9 @@ import {PodListActionBarController} from 'podlist/podlistactionbar_controller'; import podListModule from 'podlist/podlist_module'; import {stateName as deploy} from 'deploy/deploy_state'; -describe('Replica Set List Action Bar controller', () => { +describe('Pod List Action Bar controller', () => { /** - * Replica Set List controller. + * Pod List controller. * @type {!PodListController} */ let ctrl;