Skip to content

Commit 5b450ec

Browse files
Sebastian FlorekSebastian Florek
authored andcommitted
Add related pods to service detail page. Refactor heapster client to allow testing. (#739)
1 parent 597f6e4 commit 5b450ec

File tree

7 files changed

+152
-15
lines changed

7 files changed

+152
-15
lines changed

src/app/backend/client/heapsterclient.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,13 @@ type HeapsterClient interface {
2626
// Creates a new GET HTTP request to heapster, specified by the path param, to the V1 API
2727
// endpoint. The path param is without the API prefix, e.g.,
2828
// /model/namespaces/default/pod-list/foo/metrics/memory-usage
29+
Get(path string) RequestInterface
30+
}
2931

30-
Get(path string) *restclient.Request
32+
// RequestInterface is an interface that allows to make operations on pure request object.
33+
// Separation is done to allow testing.
34+
type RequestInterface interface {
35+
DoRaw() ([]byte, error)
3136
}
3237

3338
// InClusterHeapsterClient is an in-cluster implementation of a Heapster client. Talks with Heapster
@@ -37,7 +42,7 @@ type InClusterHeapsterClient struct {
3742
}
3843

3944
// InClusterHeapsterClient.Get creates request to given path.
40-
func (c InClusterHeapsterClient) Get(path string) *restclient.Request {
45+
func (c InClusterHeapsterClient) Get(path string) RequestInterface {
4146
return c.client.Get().Prefix("proxy").
4247
Namespace("kube-system").
4348
Resource("services").
@@ -52,7 +57,7 @@ type RemoteHeapsterClient struct {
5257
}
5358

5459
// RemoteHeapsterClient.Get creates request to given path.
55-
func (c RemoteHeapsterClient) Get(path string) *restclient.Request {
60+
func (c RemoteHeapsterClient) Get(path string) RequestInterface {
5661
return c.client.Get().Suffix(path)
5762
}
5863

src/app/backend/handler/apihandler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,8 @@ func (apiHandler *ApiHandler) handleGetServiceList(request *restful.Request, res
300300
func (apiHandler *ApiHandler) handleGetServiceDetail(request *restful.Request, response *restful.Response) {
301301
namespace := request.PathParameter("namespace")
302302
service := request.PathParameter("service")
303-
result, err := resourceService.GetServiceDetail(apiHandler.client, namespace, service)
303+
result, err := resourceService.GetServiceDetail(apiHandler.client, apiHandler.heapsterClient,
304+
namespace, service)
304305
if err != nil {
305306
handleInternalError(response, err)
306307
return

src/app/backend/resource/service/servicedetail.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ import (
1818
"log"
1919

2020
"k8s.io/kubernetes/pkg/api"
21-
client "k8s.io/kubernetes/pkg/client/unversioned"
21+
k8sClient "k8s.io/kubernetes/pkg/client/unversioned"
2222

23+
"github.com/kubernetes/dashboard/client"
2324
"github.com/kubernetes/dashboard/resource/common"
25+
"github.com/kubernetes/dashboard/resource/pod"
2426
)
2527

2628
// Service is a representation of a service.
@@ -45,10 +47,15 @@ type ServiceDetail struct {
4547
// ClusterIP is usually assigned by the master. Valid values are None, empty string (""), or
4648
// a valid IP address. None can be specified for headless services when proxying is not required
4749
ClusterIP string `json:"clusterIP"`
50+
51+
// PodList represents list of pods targeted by same label selector as this service.
52+
PodList pod.PodList `json:"podList"`
4853
}
4954

5055
// GetServiceDetail gets service details.
51-
func GetServiceDetail(client client.Interface, namespace, name string) (*ServiceDetail, error) {
56+
func GetServiceDetail(client k8sClient.Interface, heapsterClient client.HeapsterClient,
57+
namespace, name string) (*ServiceDetail, error) {
58+
5259
log.Printf("Getting details of %s service in %s namespace", name, namespace)
5360

5461
// TODO(maciaszczykm): Use channels.
@@ -57,6 +64,32 @@ func GetServiceDetail(client client.Interface, namespace, name string) (*Service
5764
return nil, err
5865
}
5966

67+
podList, err := GetServicePods(client, heapsterClient, namespace, serviceData.Spec.Selector)
68+
if err != nil {
69+
return nil, err
70+
}
71+
6072
service := ToServiceDetail(serviceData)
73+
service.PodList = *podList
74+
6175
return &service, nil
6276
}
77+
78+
// GetServicePods gets list of pods targeted by given label selector in given namespace.
79+
func GetServicePods(client k8sClient.Interface, heapsterClient client.HeapsterClient,
80+
namespace string, serviceSelector map[string]string) (*pod.PodList, error) {
81+
82+
channels := &common.ResourceChannels{
83+
PodList: common.GetPodListChannel(client, 1),
84+
}
85+
86+
apiPodList := <-channels.PodList.List
87+
if err := <-channels.PodList.Error; err != nil {
88+
return nil, err
89+
}
90+
91+
apiPods := common.FilterNamespacedPodsBySelector(apiPodList.Items, namespace, serviceSelector)
92+
podList := pod.CreatePodList(apiPods, heapsterClient)
93+
94+
return &podList, nil
95+
}

src/app/externs/backendapi.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ backendApi.Pod;
284284
* externalEndpoints: !Array<!backendApi.Endpoint>,
285285
* selector: !Object<string, string>,
286286
* type: string,
287-
* clusterIP: string
287+
* clusterIP: string,
288+
* podList: !backendApi.PodList
288289
* }}
289290
*/
290291
backendApi.ServiceDetail;

src/app/frontend/servicedetail/servicedetail.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@
1515
-->
1616

1717
<kd-service-info service="ctrl.serviceDetail"></kd-service-info>
18+
19+
<kd-content-card ng-if="ctrl.serviceDetail.podList.pods.length">
20+
<kd-title>Pods</kd-title>
21+
<kd-content>
22+
<kd-pod-card-list pod-list="ctrl.serviceDetail.podList"></kd-pod-card-list>
23+
</kd-content>
24+
</kd-content-card>

src/test/backend/resource/replicaset/replicasetdetail_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,22 @@ import (
1919
"testing"
2020

2121
"k8s.io/kubernetes/pkg/api"
22+
"k8s.io/kubernetes/pkg/api/unversioned"
2223
"k8s.io/kubernetes/pkg/apis/extensions"
24+
"k8s.io/kubernetes/pkg/client/restclient"
2325
k8sClient "k8s.io/kubernetes/pkg/client/unversioned"
2426
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
2527

28+
"github.com/kubernetes/dashboard/client"
2629
"github.com/kubernetes/dashboard/resource/common"
2730
"github.com/kubernetes/dashboard/resource/pod"
28-
"k8s.io/kubernetes/pkg/api/unversioned"
29-
"k8s.io/kubernetes/pkg/client/restclient"
3031
)
3132

3233
type FakeHeapsterClient struct {
3334
client k8sClient.Interface
3435
}
3536

36-
func (c FakeHeapsterClient) Get(path string) *restclient.Request {
37+
func (c FakeHeapsterClient) Get(path string) client.RequestInterface {
3738
return &restclient.Request{}
3839
}
3940

src/test/backend/resource/service/servicedetail_test.go

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,28 @@ import (
1919
"testing"
2020

2121
"k8s.io/kubernetes/pkg/api"
22+
k8sClient "k8s.io/kubernetes/pkg/client/unversioned"
2223
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
2324

25+
"github.com/kubernetes/dashboard/client"
2426
"github.com/kubernetes/dashboard/resource/common"
27+
"github.com/kubernetes/dashboard/resource/pod"
2528
)
2629

30+
type FakeHeapsterClient struct {
31+
client k8sClient.Interface
32+
}
33+
34+
type FakeRequest struct{}
35+
36+
func (FakeRequest) DoRaw() ([]byte, error) {
37+
return nil, nil
38+
}
39+
40+
func (c FakeHeapsterClient) Get(path string) client.RequestInterface {
41+
return FakeRequest{}
42+
}
43+
2744
func TestGetServiceDetail(t *testing.T) {
2845
cases := []struct {
2946
service *api.Service
@@ -33,32 +50,35 @@ func TestGetServiceDetail(t *testing.T) {
3350
}{
3451
{
3552
service: &api.Service{},
36-
namespace: "test-namespace", name: "test-name",
37-
expectedActions: []string{"get"},
53+
namespace: "test-namespace-1", name: "test-name",
54+
expectedActions: []string{"get", "list"},
3855
expected: &ServiceDetail{
3956
TypeMeta: common.TypeMeta{Kind: common.ResourceKindService},
57+
PodList: pod.PodList{Pods: []pod.Pod{}},
4058
},
4159
}, {
4260
service: &api.Service{ObjectMeta: api.ObjectMeta{
4361
Name: "test-service", Namespace: "test-namespace",
4462
}},
45-
namespace: "test-namespace", name: "test-name",
46-
expectedActions: []string{"get"},
63+
namespace: "test-namespace-2", name: "test-name",
64+
expectedActions: []string{"get", "list"},
4765
expected: &ServiceDetail{
4866
ObjectMeta: common.ObjectMeta{
4967
Name: "test-service",
5068
Namespace: "test-namespace",
5169
},
5270
TypeMeta: common.TypeMeta{Kind: common.ResourceKindService},
5371
InternalEndpoint: common.Endpoint{Host: "test-service.test-namespace"},
72+
PodList: pod.PodList{Pods: []pod.Pod{}},
5473
},
5574
},
5675
}
5776

5877
for _, c := range cases {
5978
fakeClient := testclient.NewSimpleFake(c.service)
79+
fakeHeapsterClient := FakeHeapsterClient{client: testclient.NewSimpleFake()}
6080

61-
actual, _ := GetServiceDetail(fakeClient, c.namespace, c.name)
81+
actual, _ := GetServiceDetail(fakeClient, fakeHeapsterClient, c.namespace, c.name)
6282

6383
actions := fakeClient.Actions()
6484
if len(actions) != len(c.expectedActions) {
@@ -80,3 +100,72 @@ func TestGetServiceDetail(t *testing.T) {
80100
}
81101
}
82102
}
103+
104+
func TestGetServicePods(t *testing.T) {
105+
firstSelector := map[string]string{"app": "selector-1"}
106+
secondSelector := map[string]string{"app": "selector-2"}
107+
cases := []struct {
108+
namespace string
109+
serviceSelector map[string]string
110+
podList *api.PodList
111+
expectedActions []string
112+
expected *pod.PodList
113+
}{
114+
{
115+
"test-namespace-1", firstSelector,
116+
&api.PodList{Items: []api.Pod{}}, []string{"list"}, &pod.PodList{Pods: []pod.Pod{}},
117+
}, {
118+
"test-namespace-2",
119+
firstSelector,
120+
&api.PodList{Items: []api.Pod{{ObjectMeta: api.ObjectMeta{
121+
Name: "test-pod",
122+
Labels: secondSelector,
123+
}}}},
124+
[]string{"list"},
125+
&pod.PodList{Pods: []pod.Pod{}},
126+
}, {
127+
"test-namespace-3",
128+
firstSelector,
129+
&api.PodList{Items: []api.Pod{{ObjectMeta: api.ObjectMeta{
130+
Name: "test-pod",
131+
Labels: firstSelector,
132+
Namespace: "test-namespace-3",
133+
}}}},
134+
[]string{"list"},
135+
&pod.PodList{Pods: []pod.Pod{{
136+
ObjectMeta: common.ObjectMeta{
137+
Name: "test-pod",
138+
Labels: firstSelector,
139+
Namespace: "test-namespace-3",
140+
},
141+
TypeMeta: common.TypeMeta{Kind: common.ResourceKindPod},
142+
}}},
143+
},
144+
}
145+
146+
for _, c := range cases {
147+
fakeClient := testclient.NewSimpleFake(c.podList)
148+
fakeHeapsterClient := FakeHeapsterClient{client: testclient.NewSimpleFake()}
149+
150+
actual, _ := GetServicePods(fakeClient, fakeHeapsterClient, c.namespace, c.serviceSelector)
151+
152+
actions := fakeClient.Actions()
153+
if len(actions) != len(c.expectedActions) {
154+
t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions,
155+
len(c.expectedActions), len(actions))
156+
continue
157+
}
158+
159+
for i, verb := range c.expectedActions {
160+
if actions[i].GetVerb() != verb {
161+
t.Errorf("Unexpected action: %+v, expected %s",
162+
actions[i], verb)
163+
}
164+
}
165+
166+
if !reflect.DeepEqual(actual, c.expected) {
167+
t.Errorf("GetServicePods(client, heapsterClient, %#v, %#v) == \ngot %#v, \nexpected %#v",
168+
c.namespace, c.serviceSelector, actual, c.expected)
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)