Skip to content

Commit cf3ed3e

Browse files
committed
Merge pull request #750 from Draiken/deployment-detail-backend
Deployment detail backend
2 parents d19f802 + b0758e7 commit cf3ed3e

File tree

6 files changed

+422
-9
lines changed

6 files changed

+422
-9
lines changed

src/app/backend/handler/apihandler.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ func CreateHttpApiHandler(client *client.Client, heapsterClient HeapsterClient,
175175
To(apiHandler.handleGetDeployments).
176176
Writes(deployment.DeploymentList{}))
177177

178+
apiV1Ws.Route(
179+
apiV1Ws.GET("/deployment/{namespace}/{deployment}").
180+
To(apiHandler.handleGetDeploymentDetail).
181+
Writes(deployment.DeploymentDetail{}))
182+
178183
apiV1Ws.Route(
179184
apiV1Ws.GET("/daemonset").
180185
To(apiHandler.handleGetDaemonSetList).
@@ -420,6 +425,22 @@ func (apiHandler *ApiHandler) handleGetDeployments(
420425
response.WriteHeaderAndEntity(http.StatusCreated, result)
421426
}
422427

428+
// Handles get Deployment detail API call.
429+
func (apiHandler *ApiHandler) handleGetDeploymentDetail(
430+
request *restful.Request, response *restful.Response) {
431+
432+
namespace := request.PathParameter("namespace")
433+
name := request.PathParameter("deployment")
434+
result, err := deployment.GetDeploymentDetail(apiHandler.client, namespace,
435+
name)
436+
if err != nil {
437+
handleInternalError(response, err)
438+
return
439+
}
440+
441+
response.WriteHeaderAndEntity(http.StatusOK, result)
442+
}
443+
423444
// Handles get Pod list API call.
424445
func (apiHandler *ApiHandler) handleGetPods(
425446
request *restful.Request, response *restful.Response) {
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package deployment
2+
3+
import (
4+
"log"
5+
6+
"github.com/kubernetes/dashboard/resource/common"
7+
"github.com/kubernetes/dashboard/resource/replicaset"
8+
"k8s.io/kubernetes/pkg/api"
9+
"k8s.io/kubernetes/pkg/apis/extensions"
10+
client "k8s.io/kubernetes/pkg/client/unversioned"
11+
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
12+
)
13+
14+
type RollingUpdateStrategy struct {
15+
MaxSurge int `json:"maxSurge"`
16+
MaxUnavailable int `json:"maxUnavailable"`
17+
}
18+
19+
type StatusInfo struct {
20+
// Total number of desired replicas on the deployment
21+
Replicas int `json:"replicas"`
22+
23+
// Number of non-terminated pods that have the desired template spec
24+
Updated int `json:"updated"`
25+
26+
// Number of available pods (ready for at least minReadySeconds)
27+
// targeted by this deployment
28+
Available int `json:"available"`
29+
30+
// Total number of unavailable pods targeted by this deployment.
31+
Unavailable int `json:"unavailable"`
32+
}
33+
34+
// ReplicaSetDetail is a presentation layer view of Kubernetes Replica Set resource. This means
35+
type DeploymentDetail struct {
36+
ObjectMeta common.ObjectMeta `json:"objectMeta"`
37+
TypeMeta common.TypeMeta `json:"typeMeta"`
38+
39+
// Label selector of the service.
40+
Selector map[string]string `json:"selector"`
41+
42+
// Status information on the deployment
43+
StatusInfo `json:"statusInfo"`
44+
45+
// The deployment strategy to use to replace existing pods with new ones.
46+
// Valid options: Recreate, RollingUpdate
47+
Strategy string `json:"strategy"`
48+
49+
// Min ready seconds
50+
MinReadySeconds int `json:"minReadySeconds"`
51+
52+
// Rolling update strategy containing maxSurge and maxUnavailable
53+
RollingUpdateStrategy `json:"rollingUpdateStrategy,omitempty"`
54+
55+
// RepliaSetList containing old replica sets from the deployment
56+
OldReplicaSetList replicaset.ReplicaSetList `json:"oldReplicaSetList"`
57+
58+
// New replica set used by this deployment
59+
NewReplicaSet replicaset.ReplicaSet `json:"newReplicaSet"`
60+
61+
// List of events related to this Deployment
62+
EventList common.EventList `json:"eventList"`
63+
}
64+
65+
func GetDeploymentDetail(client client.Interface, namespace string,
66+
name string) (*DeploymentDetail, error) {
67+
68+
log.Printf("Getting details of %s deployment in %s namespace", name, namespace)
69+
70+
deploymentData, err := client.Extensions().Deployments(namespace).Get(name)
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
channels := &common.ResourceChannels{
76+
ReplicaSetList: common.GetReplicaSetListChannel(client.Extensions(), 1),
77+
PodList: common.GetPodListChannel(client, 1),
78+
}
79+
80+
replicaSetList := <-channels.ReplicaSetList.List
81+
if err := <-channels.ReplicaSetList.Error; err != nil {
82+
return nil, err
83+
}
84+
85+
pods := <-channels.PodList.List
86+
if err := <-channels.PodList.Error; err != nil {
87+
return nil, err
88+
}
89+
90+
oldReplicaSets, _, err := deploymentutil.FindOldReplicaSets(
91+
deploymentData, replicaSetList.Items, pods)
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
newReplicaSet, err := deploymentutil.FindNewReplicaSet(deploymentData, replicaSetList.Items)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
events, err := GetDeploymentEvents(client, namespace, name)
102+
if err != nil {
103+
return nil, err
104+
}
105+
106+
return getDeploymentDetail(deploymentData, oldReplicaSets, newReplicaSet,
107+
pods.Items, events), nil
108+
}
109+
110+
func getDeploymentDetail(deployment *extensions.Deployment,
111+
oldRs []*extensions.ReplicaSet, newRs *extensions.ReplicaSet,
112+
pods []api.Pod, events *common.EventList) *DeploymentDetail {
113+
114+
newRsPodInfo := common.GetPodInfo(newRs.Status.Replicas, newRs.Spec.Replicas, pods)
115+
newReplicaSet := replicaset.ToReplicaSet(newRs, &newRsPodInfo)
116+
117+
oldReplicaSets := make([]extensions.ReplicaSet, len(oldRs))
118+
for i, replicaSet := range oldRs {
119+
oldReplicaSets[i] = *replicaSet
120+
}
121+
oldReplicaSetList := replicaset.ToReplicaSetList(oldReplicaSets,
122+
[]api.Service{}, pods, []api.Event{}, []api.Node{})
123+
124+
return &DeploymentDetail{
125+
ObjectMeta: common.NewObjectMeta(deployment.ObjectMeta),
126+
TypeMeta: common.NewTypeMeta(common.ResourceKindDeployment),
127+
Selector: deployment.Spec.Selector.MatchLabels,
128+
StatusInfo: GetStatusInfo(&deployment.Status),
129+
Strategy: string(deployment.Spec.Strategy.Type),
130+
MinReadySeconds: deployment.Spec.MinReadySeconds,
131+
RollingUpdateStrategy: RollingUpdateStrategy{
132+
MaxSurge: deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue(),
133+
MaxUnavailable: deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue(),
134+
},
135+
OldReplicaSetList: *oldReplicaSetList,
136+
NewReplicaSet: newReplicaSet,
137+
EventList: *events,
138+
}
139+
}
140+
141+
func GetStatusInfo(deploymentStatus *extensions.DeploymentStatus) StatusInfo {
142+
return StatusInfo{
143+
Replicas: deploymentStatus.Replicas,
144+
Updated: deploymentStatus.UpdatedReplicas,
145+
Available: deploymentStatus.AvailableReplicas,
146+
Unavailable: deploymentStatus.UnavailableReplicas,
147+
}
148+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package deployment
2+
3+
import (
4+
"log"
5+
6+
"github.com/kubernetes/dashboard/resource/common"
7+
"github.com/kubernetes/dashboard/resource/event"
8+
client "k8s.io/kubernetes/pkg/client/unversioned"
9+
)
10+
11+
func GetDeploymentEvents(client client.Interface, namespace string, deploymentName string) (*common.EventList, error) {
12+
13+
log.Printf("Getting events related to %s deployment in %s namespace", deploymentName,
14+
namespace)
15+
16+
// Get events for deployment.
17+
dpEvents, err := event.GetEvents(client, namespace, deploymentName)
18+
if err != nil {
19+
return nil, err
20+
}
21+
22+
if !event.IsTypeFilled(dpEvents) {
23+
dpEvents = event.FillEventsType(dpEvents)
24+
}
25+
26+
events := event.AppendEvents(dpEvents, common.EventList{
27+
Namespace: namespace,
28+
Events: make([]common.Event, 0),
29+
})
30+
31+
log.Printf("Found %d events related to %s deployment in %s namespace",
32+
len(events.Events), deploymentName, namespace)
33+
34+
return &events, nil
35+
}

src/app/backend/resource/replicaset/replicasetlist.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ func GetReplicaSetListFromChannels(channels *common.ResourceChannels) (
9999
return nil, err
100100
}
101101

102-
return getReplicaSetList(replicaSets.Items, services.Items, pods.Items, events.Items,
102+
return ToReplicaSetList(replicaSets.Items, services.Items, pods.Items, events.Items,
103103
nodes.Items), nil
104104
}
105105

106-
func getReplicaSetList(replicaSets []extensions.ReplicaSet,
106+
func ToReplicaSetList(replicaSets []extensions.ReplicaSet,
107107
services []api.Service, pods []api.Pod, events []api.Event,
108108
nodes []api.Node) *ReplicaSetList {
109109

@@ -116,14 +116,17 @@ func getReplicaSetList(replicaSets []extensions.ReplicaSet,
116116
replicaSet.Spec.Selector.MatchLabels)
117117
podInfo := getPodInfo(&replicaSet, matchingPods)
118118

119-
replicaSetList.ReplicaSets = append(replicaSetList.ReplicaSets,
120-
ReplicaSet{
121-
ObjectMeta: common.NewObjectMeta(replicaSet.ObjectMeta),
122-
TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet),
123-
ContainerImages: common.GetContainerImages(&replicaSet.Spec.Template.Spec),
124-
Pods: podInfo,
125-
})
119+
replicaSetList.ReplicaSets = append(replicaSetList.ReplicaSets, ToReplicaSet(&replicaSet, &podInfo))
126120
}
127121

128122
return replicaSetList
129123
}
124+
125+
func ToReplicaSet(replicaSet *extensions.ReplicaSet, podInfo *common.PodInfo) ReplicaSet {
126+
return ReplicaSet{
127+
ObjectMeta: common.NewObjectMeta(replicaSet.ObjectMeta),
128+
TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet),
129+
ContainerImages: common.GetContainerImages(&replicaSet.Spec.Template.Spec),
130+
Pods: *podInfo,
131+
}
132+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package deployment
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"k8s.io/kubernetes/pkg/api"
8+
"k8s.io/kubernetes/pkg/api/unversioned"
9+
"k8s.io/kubernetes/pkg/apis/extensions"
10+
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
11+
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
12+
"k8s.io/kubernetes/pkg/util/intstr"
13+
14+
"github.com/kubernetes/dashboard/resource/common"
15+
"github.com/kubernetes/dashboard/resource/replicaset"
16+
)
17+
18+
func TestGetDeploymentDetail(t *testing.T) {
19+
podList := &api.PodList{}
20+
eventList := &api.EventList{}
21+
22+
deployment := &extensions.Deployment{
23+
ObjectMeta: api.ObjectMeta{
24+
Name: "test-name",
25+
Labels: map[string]string{"track": "beta"},
26+
},
27+
Spec: extensions.DeploymentSpec{
28+
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
29+
Replicas: 4,
30+
MinReadySeconds: 5,
31+
Strategy: extensions.DeploymentStrategy{
32+
Type: extensions.RollingUpdateDeploymentStrategyType,
33+
RollingUpdate: &extensions.RollingUpdateDeployment{
34+
MaxSurge: intstr.FromInt(1),
35+
MaxUnavailable: intstr.FromString("1"),
36+
},
37+
},
38+
Template: api.PodTemplateSpec{
39+
ObjectMeta: api.ObjectMeta{
40+
Name: "test-pod-name",
41+
Labels: map[string]string{"track": "beta"},
42+
},
43+
},
44+
},
45+
Status: extensions.DeploymentStatus{
46+
Replicas: 4,
47+
UpdatedReplicas: 2,
48+
AvailableReplicas: 3,
49+
UnavailableReplicas: 1,
50+
},
51+
}
52+
53+
podTemplateSpec := deploymentutil.GetNewReplicaSetTemplate(deployment)
54+
55+
newReplicaSet := extensions.ReplicaSet{
56+
ObjectMeta: api.ObjectMeta{Name: "replica-set-1"},
57+
Spec: extensions.ReplicaSetSpec{
58+
Template: podTemplateSpec,
59+
},
60+
}
61+
62+
replicaSetList := &extensions.ReplicaSetList{
63+
Items: []extensions.ReplicaSet{
64+
newReplicaSet,
65+
{
66+
ObjectMeta: api.ObjectMeta{Name: "replica-set-2"},
67+
},
68+
},
69+
}
70+
71+
cases := []struct {
72+
namespace, name string
73+
expectedActions []string
74+
deployment *extensions.Deployment
75+
expected *DeploymentDetail
76+
}{
77+
{
78+
"test-namespace", "test-name",
79+
[]string{"get", "list", "list", "list"},
80+
deployment,
81+
&DeploymentDetail{
82+
ObjectMeta: common.ObjectMeta{
83+
Name: "test-name",
84+
Labels: map[string]string{"track": "beta"},
85+
},
86+
TypeMeta: common.TypeMeta{Kind: common.ResourceKindDeployment},
87+
Selector: map[string]string{"foo": "bar"},
88+
StatusInfo: StatusInfo{
89+
Replicas: 4,
90+
Updated: 2,
91+
Available: 3,
92+
Unavailable: 1,
93+
},
94+
Strategy: "RollingUpdate",
95+
MinReadySeconds: 5,
96+
RollingUpdateStrategy: RollingUpdateStrategy{
97+
MaxSurge: 1,
98+
MaxUnavailable: 1,
99+
},
100+
OldReplicaSetList: replicaset.ReplicaSetList{ReplicaSets: []replicaset.ReplicaSet{}},
101+
NewReplicaSet: replicaset.ReplicaSet{
102+
ObjectMeta: common.NewObjectMeta(newReplicaSet.ObjectMeta),
103+
TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet),
104+
Pods: common.PodInfo{Warnings: []common.Event{}},
105+
},
106+
EventList: common.EventList{
107+
Namespace: "test-namespace",
108+
Events: []common.Event{},
109+
},
110+
},
111+
},
112+
}
113+
114+
for _, c := range cases {
115+
116+
fakeClient := testclient.NewSimpleFake(c.deployment, replicaSetList, podList, eventList)
117+
118+
actual, _ := GetDeploymentDetail(fakeClient, c.namespace, c.name)
119+
120+
actions := fakeClient.Actions()
121+
if len(actions) != len(c.expectedActions) {
122+
t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions,
123+
len(c.expectedActions), len(actions))
124+
continue
125+
}
126+
127+
for i, verb := range c.expectedActions {
128+
if actions[i].GetVerb() != verb {
129+
t.Errorf("Unexpected action: %+v, expected %s",
130+
actions[i], verb)
131+
}
132+
}
133+
134+
if !reflect.DeepEqual(actual, c.expected) {
135+
t.Errorf("GetDeploymentDetail(client, namespace, name) == \ngot: %#v, \nexpected %#v",
136+
actual, c.expected)
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)