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);