diff --git a/src/app/backend/handler/apihandler.go b/src/app/backend/handler/apihandler.go index b4dbfe61df41c01685dfe952b9c52a2f0ce4a8dc..7c404084291f0f3f433a4aaa6c6905b4fe11d3d1 100644 --- a/src/app/backend/handler/apihandler.go +++ b/src/app/backend/handler/apihandler.go @@ -253,6 +253,10 @@ func CreateHTTPAPIHandler(iManager integration.IntegrationManager, cManager clie apiV1Ws.GET("/pod/{namespace}/{pod}/shell/{container}"). To(apiHandler.handleExecShell). Writes(TerminalResponse{})) + apiV1Ws.Route( + apiV1Ws.GET("/pod/{namespace}/{pod}/persistentvolumeclaim"). + To(apiHandler.handleGetPodPersistentVolumeClaims). + Writes(persistentvolumeclaim.PersistentVolumeClaimList{})) apiV1Ws.Route( apiV1Ws.GET("/deployment"). @@ -2150,6 +2154,26 @@ func (apiHandler *APIHandler) handleGetStorageClassPersistentVolumes(request *re response.WriteHeaderAndEntity(http.StatusOK, result) } +func (apiHandler *APIHandler) handleGetPodPersistentVolumeClaims(request *restful.Request, + response *restful.Response) { + k8sClient, err := apiHandler.cManager.Client(request) + if err != nil { + handleInternalError(response, err) + return + } + + name := request.PathParameter("pod") + namespace := request.PathParameter("namespace") + dataSelect := parseDataSelectPathParameter(request) + result, err := persistentvolumeclaim.GetPodPersistentVolumeClaims(k8sClient, + namespace, name, dataSelect) + if err != nil { + handleInternalError(response, err) + return + } + response.WriteHeaderAndEntity(http.StatusOK, result) +} + func (apiHandler *APIHandler) handleLogSource(request *restful.Request, response *restful.Response) { k8sClient, err := apiHandler.cManager.Client(request) if err != nil { diff --git a/src/app/backend/resource/persistentvolumeclaim/common.go b/src/app/backend/resource/persistentvolumeclaim/common.go index 3bb354c81a987aa193d7cc63c7b57adab9f87da3..367c78b3cc12359a1ede068d5a120920fd95a94c 100644 --- a/src/app/backend/resource/persistentvolumeclaim/common.go +++ b/src/app/backend/resource/persistentvolumeclaim/common.go @@ -15,14 +15,77 @@ package persistentvolumeclaim import ( + "log" + "strings" + + "github.com/kubernetes/dashboard/src/app/backend/errors" + "github.com/kubernetes/dashboard/src/app/backend/resource/common" "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" api "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + client "k8s.io/client-go/kubernetes" ) // The code below allows to perform complex data section on []api.PersistentVolumeClaim type PersistentVolumeClaimCell api.PersistentVolumeClaim +// GetPodPersistentVolumeClaims gets persistentvolumeclaims that are associated with this pod. +func GetPodPersistentVolumeClaims(client client.Interface, namespace string, podName string, + dsQuery *dataselect.DataSelectQuery) (*PersistentVolumeClaimList, error) { + + pod, err := client.CoreV1().Pods(namespace).Get(podName, metaV1.GetOptions{}) + if err != nil { + return nil, err + } + + claimNames := make([]string, 0) + if pod.Spec.Volumes != nil && len(pod.Spec.Volumes) > 0 { + for _, v := range pod.Spec.Volumes { + persistentVolumeClaim := v.PersistentVolumeClaim + if persistentVolumeClaim != nil { + claimNames = append(claimNames, persistentVolumeClaim.ClaimName) + } + } + } + + if len(claimNames) > 0 { + channels := &common.ResourceChannels{ + PersistentVolumeClaimList: common.GetPersistentVolumeClaimListChannel( + client, common.NewSameNamespaceQuery(namespace), 1), + } + + persistentVolumeClaimList := <-channels.PersistentVolumeClaimList.List + + err = <-channels.PersistentVolumeClaimList.Error + nonCriticalErrors, criticalError := errors.HandleError(err) + if criticalError != nil { + return nil, criticalError + } + + podPersistentVolumeClaims := make([]api.PersistentVolumeClaim, 0) + for _, pvc := range persistentVolumeClaimList.Items { + for _, claimName := range claimNames { + if strings.Compare(claimName, pvc.Name) == 0 { + podPersistentVolumeClaims = append(podPersistentVolumeClaims, pvc) + break + } + } + } + + log.Printf("Found %d persistentvolumeclaims related to %s pod", + len(podPersistentVolumeClaims), podName) + + return toPersistentVolumeClaimList(podPersistentVolumeClaims, + nonCriticalErrors, dsQuery), nil + } + + log.Printf("No persistentvolumeclaims found related to %s pod", podName) + + // No ClaimNames found in Pod details, return empty response. + return &PersistentVolumeClaimList{}, nil +} + func (self PersistentVolumeClaimCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue { switch name { case dataselect.NameProperty: diff --git a/src/app/backend/resource/persistentvolumeclaim/common_test.go b/src/app/backend/resource/persistentvolumeclaim/common_test.go new file mode 100644 index 0000000000000000000000000000000000000000..156c0b0393812d8c0ca1b4986a8365790a74b4b8 --- /dev/null +++ b/src/app/backend/resource/persistentvolumeclaim/common_test.go @@ -0,0 +1,84 @@ +// Copyright 2017 The Kubernetes Authors. +// +// 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 persistentvolumeclaim + +import ( + "reflect" + "testing" + + "github.com/kubernetes/dashboard/src/app/backend/api" + "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + v1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestGetPodPersistentVolumeClaims(t *testing.T) { + cases := []struct { + pod *v1.Pod + name string + namespace string + persistentVolumeClaimList *v1.PersistentVolumeClaimList + expected *PersistentVolumeClaimList + }{ + { + pod: &v1.Pod{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "test-pod", Namespace: "test-namespace", Labels: map[string]string{"app": "test"}, + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{{ + Name: "vol-1", + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "pvc-1", + }, + }, + }}, + }, + }, + name: "test-pod", + namespace: "test-namespace", + persistentVolumeClaimList: &v1.PersistentVolumeClaimList{Items: []v1.PersistentVolumeClaim{ + { + ObjectMeta: metaV1.ObjectMeta{ + Name: "pvc-1", Namespace: "test-namespace", Labels: map[string]string{"app": "test"}, + }, + }, + }}, + expected: &PersistentVolumeClaimList{ + ListMeta: api.ListMeta{TotalItems: 1}, + Items: []PersistentVolumeClaim{{ + TypeMeta: api.TypeMeta{Kind: api.ResourceKindPersistentVolumeClaim}, + ObjectMeta: api.ObjectMeta{Name: "pvc-1", Namespace: "test-namespace", + Labels: map[string]string{"app": "test"}}, + }}, + Errors: []error{}, + }, + }, + } + + for _, c := range cases { + + fakeClient := fake.NewSimpleClientset(c.persistentVolumeClaimList, c.pod) + + actual, _ := GetPodPersistentVolumeClaims(fakeClient, c.namespace, c.name, dataselect.NoDataSelect) + + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("GetPodPersistentVolumeClaims(client, %#v, %#v) == \ngot: %#v, \nexpected %#v", + c.name, c.namespace, actual, c.expected) + } + } +} diff --git a/src/app/backend/resource/pod/detail.go b/src/app/backend/resource/pod/detail.go index 26bda139b6bfd8964ac09fed6ec979867ccef132..7849a1afb56bd0f102aed4c6631d16e75a4cd98f 100644 --- a/src/app/backend/resource/pod/detail.go +++ b/src/app/backend/resource/pod/detail.go @@ -27,6 +27,7 @@ import ( "github.com/kubernetes/dashboard/src/app/backend/resource/common" "github.com/kubernetes/dashboard/src/app/backend/resource/controller" "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + "github.com/kubernetes/dashboard/src/app/backend/resource/persistentvolumeclaim" "k8s.io/api/core/v1" res "k8s.io/apimachinery/pkg/api/resource" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -36,19 +37,20 @@ import ( // PodDetail is a presentation layer view of Kubernetes Pod resource. type PodDetail struct { - ObjectMeta api.ObjectMeta `json:"objectMeta"` - TypeMeta api.TypeMeta `json:"typeMeta"` - PodPhase v1.PodPhase `json:"podPhase"` - PodIP string `json:"podIP"` - NodeName string `json:"nodeName"` - RestartCount int32 `json:"restartCount"` - QOSClass string `json:"qosClass"` - Controller controller.ResourceOwner `json:"controller"` - Containers []Container `json:"containers"` - InitContainers []Container `json:"initContainers"` - Metrics []metricapi.Metric `json:"metrics"` - Conditions []common.Condition `json:"conditions"` - EventList common.EventList `json:"eventList"` + ObjectMeta api.ObjectMeta `json:"objectMeta"` + TypeMeta api.TypeMeta `json:"typeMeta"` + PodPhase v1.PodPhase `json:"podPhase"` + PodIP string `json:"podIP"` + NodeName string `json:"nodeName"` + RestartCount int32 `json:"restartCount"` + QOSClass string `json:"qosClass"` + Controller controller.ResourceOwner `json:"controller"` + Containers []Container `json:"containers"` + InitContainers []Container `json:"initContainers"` + Metrics []metricapi.Metric `json:"metrics"` + Conditions []common.Condition `json:"conditions"` + EventList common.EventList `json:"eventList"` + PersistentvolumeclaimList persistentvolumeclaim.PersistentVolumeClaimList `json:"persistentVolumeClaimList"` // List of non-critical errors, that occurred during resource retrieval. Errors []error `json:"errors"` @@ -131,7 +133,15 @@ func GetPodDetail(client kubernetes.Interface, metricClient metricapi.MetricClie return nil, criticalError } - podDetail := toPodDetail(pod, metrics, configMapList, secretList, controller, eventList, nonCriticalErrors) + persistentVolumeClaimList, err := persistentvolumeclaim.GetPodPersistentVolumeClaims(client, + namespace, name, dataselect.DefaultDataSelect) + nonCriticalErrors, criticalError = errorHandler.AppendError(err, nonCriticalErrors) + if criticalError != nil { + return nil, criticalError + } + + podDetail := toPodDetail(pod, metrics, configMapList, secretList, controller, + eventList, persistentVolumeClaimList, nonCriticalErrors) return &podDetail, nil } @@ -196,22 +206,24 @@ func extractContainerInfo(containerList []v1.Container, pod *v1.Pod, configMaps } func toPodDetail(pod *v1.Pod, metrics []metricapi.Metric, configMaps *v1.ConfigMapList, secrets *v1.SecretList, - controller controller.ResourceOwner, events *common.EventList, nonCriticalErrors []error) PodDetail { + controller controller.ResourceOwner, events *common.EventList, + persistentVolumeClaimList *persistentvolumeclaim.PersistentVolumeClaimList, nonCriticalErrors []error) PodDetail { return PodDetail{ - ObjectMeta: api.NewObjectMeta(pod.ObjectMeta), - TypeMeta: api.NewTypeMeta(api.ResourceKindPod), - PodPhase: pod.Status.Phase, - PodIP: pod.Status.PodIP, - RestartCount: getRestartCount(*pod), - QOSClass: string(pod.Status.QOSClass), - NodeName: pod.Spec.NodeName, - Controller: controller, - Containers: extractContainerInfo(pod.Spec.Containers, pod, configMaps, secrets), - InitContainers: extractContainerInfo(pod.Spec.InitContainers, pod, configMaps, secrets), - Metrics: metrics, - Conditions: getPodConditions(*pod), - EventList: *events, - Errors: nonCriticalErrors, + ObjectMeta: api.NewObjectMeta(pod.ObjectMeta), + TypeMeta: api.NewTypeMeta(api.ResourceKindPod), + PodPhase: pod.Status.Phase, + PodIP: pod.Status.PodIP, + RestartCount: getRestartCount(*pod), + QOSClass: string(pod.Status.QOSClass), + NodeName: pod.Spec.NodeName, + Controller: controller, + Containers: extractContainerInfo(pod.Spec.Containers, pod, configMaps, secrets), + InitContainers: extractContainerInfo(pod.Spec.InitContainers, pod, configMaps, secrets), + Metrics: metrics, + Conditions: getPodConditions(*pod), + EventList: *events, + PersistentvolumeclaimList: *persistentVolumeClaimList, + Errors: nonCriticalErrors, } } diff --git a/src/app/backend/resource/pod/detail_test.go b/src/app/backend/resource/pod/detail_test.go index 2840755cdcfc6ab46d92458a2e6e155f85365313..24b365233f131dccac0c1f524b361b41029522ea 100644 --- a/src/app/backend/resource/pod/detail_test.go +++ b/src/app/backend/resource/pod/detail_test.go @@ -24,6 +24,7 @@ import ( "github.com/kubernetes/dashboard/src/app/backend/resource/common" "github.com/kubernetes/dashboard/src/app/backend/resource/controller" "github.com/kubernetes/dashboard/src/app/backend/resource/dataselect" + "github.com/kubernetes/dashboard/src/app/backend/resource/persistentvolumeclaim" "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -47,12 +48,13 @@ func TestGetPodDetail(t *testing.T) { Namespace: "test-namespace", Labels: map[string]string{"app": "test"}, }, - Controller: controller.ResourceOwner{}, - Containers: []Container{}, - InitContainers: []Container{}, - EventList: common.EventList{Events: []common.Event{}}, - Metrics: []metricapi.Metric{}, - Errors: []error{}, + Controller: controller.ResourceOwner{}, + Containers: []Container{}, + InitContainers: []Container{}, + EventList: common.EventList{Events: []common.Event{}}, + Metrics: []metricapi.Metric{}, + PersistentvolumeclaimList: persistentvolumeclaim.PersistentVolumeClaimList{}, + Errors: []error{}, }, }, } diff --git a/src/app/frontend/pod/detail/controller.js b/src/app/frontend/pod/detail/controller.js index 14dadb64c0d8ef9a15c93a1cdae89a4eb0a57e6a..9ff2f074b1f3102a361ab69b8503808a6c9078d0 100644 --- a/src/app/frontend/pod/detail/controller.js +++ b/src/app/frontend/pod/detail/controller.js @@ -19,13 +19,17 @@ export class PodDetailController { /** * @param {!backendApi.PodDetail} podDetail * @param {!angular.Resource} kdPodEventsResource + * @param {!angular.Resource} kdPodPersistentVolumeClaimsResource * @ngInject */ - constructor(podDetail, kdPodEventsResource) { + constructor(podDetail, kdPodEventsResource, kdPodPersistentVolumeClaimsResource) { /** @export {!backendApi.PodDetail} */ this.podDetail = podDetail; /** @export {!angular.Resource} */ this.eventListResource = kdPodEventsResource; + + /**@export {!angular.Resource} */ + this.persistentVolumeClaimsResource = kdPodPersistentVolumeClaimsResource; } } diff --git a/src/app/frontend/pod/detail/detail.html b/src/app/frontend/pod/detail/detail.html index 4a319274ad455aef9255e98bb0feeb960df95c6a..75192b17d70a007bb6ae1e4cfa38fa7ade472c93 100644 --- a/src/app/frontend/pod/detail/detail.html +++ b/src/app/frontend/pod/detail/detail.html @@ -51,3 +51,11 @@ limitations under the License. + + + + + + + diff --git a/src/app/frontend/pod/detail/stateconfig.js b/src/app/frontend/pod/detail/stateconfig.js index 65d433d0803fd56098bd0ed73f3d2588a8dd3791..b4fc62919890d0f2534dd201ad735681564436b7 100644 --- a/src/app/frontend/pod/detail/stateconfig.js +++ b/src/app/frontend/pod/detail/stateconfig.js @@ -80,3 +80,12 @@ export function getPodDetailResource($resource, $stateParams) { export function getPodDetail(podDetailResource) { return podDetailResource.get().$promise; } + +/** + * @param {!angular.$resource} $resource + * @return {!angular.Resource} + * @ngInject + */ +export function podPersistentVolumeClaimsResource($resource) { + return $resource(`api/v1/pod/:namespace/:name/persistentvolumeclaim`); +} diff --git a/src/app/frontend/pod/module.js b/src/app/frontend/pod/module.js index f73566f8ee8c48f83c80d83bcf43fbbaf70a0c76..86c799cf354d65416db740f48389db2c56aeb25e 100644 --- a/src/app/frontend/pod/module.js +++ b/src/app/frontend/pod/module.js @@ -18,11 +18,13 @@ import filtersModule from '../common/filters/module'; import namespaceModule from '../common/namespace/module'; import configMapModule from '../configmap/module'; import eventsModule from '../events/module'; +import persistentvolumeclaimModule from '../persistentvolumeclaim/module'; import {containerInfoComponent} from './detail/containerinfo_component'; import {creatorInfoComponent} from './detail/creatorinfo_component'; import {podInfoComponent} from './detail/info_component'; import {podEventsResource} from './detail/stateconfig'; +import {podPersistentVolumeClaimsResource} from './detail/stateconfig'; import {podCardComponent} from './list/card_component'; import {podCardListComponent} from './list/cardlist_component'; import {podListResource} from './list/stateconfig'; @@ -44,6 +46,7 @@ export default angular eventsModule.name, filtersModule.name, namespaceModule.name, + persistentvolumeclaimModule.name, ]) .config(stateConfig) .component('kdContainerInfo', containerInfoComponent) @@ -52,4 +55,5 @@ export default angular .component('kdPodCardList', podCardListComponent) .component('kdPodInfo', podInfoComponent) .factory('kdPodEventsResource', podEventsResource) - .factory('kdPodListResource', podListResource); + .factory('kdPodListResource', podListResource) + .factory('kdPodPersistentVolumeClaimsResource', podPersistentVolumeClaimsResource);