Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/app/backend/handler/apihandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ func CreateHttpApiHandler(client *client.Client, heapsterClient HeapsterClient,
To(apiHandler.handleGetDeployments).
Writes(deployment.DeploymentList{}))

apiV1Ws.Route(
apiV1Ws.GET("/deployment/{namespace}/{deployment}").
To(apiHandler.handleGetDeploymentDetail).
Writes(deployment.DeploymentDetail{}))

apiV1Ws.Route(
apiV1Ws.GET("/daemonset").
To(apiHandler.handleGetDaemonSetList).
Expand Down Expand Up @@ -420,6 +425,22 @@ func (apiHandler *ApiHandler) handleGetDeployments(
response.WriteHeaderAndEntity(http.StatusCreated, result)
}

// Handles get Deployment detail API call.
func (apiHandler *ApiHandler) handleGetDeploymentDetail(
request *restful.Request, response *restful.Response) {

namespace := request.PathParameter("namespace")
name := request.PathParameter("deployment")
result, err := deployment.GetDeploymentDetail(apiHandler.client, namespace,
name)
if err != nil {
handleInternalError(response, err)
return
}

response.WriteHeaderAndEntity(http.StatusOK, result)
}

// Handles get Pod list API call.
func (apiHandler *ApiHandler) handleGetPods(
request *restful.Request, response *restful.Response) {
Expand Down
148 changes: 148 additions & 0 deletions src/app/backend/resource/deployment/deploymentdetail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package deployment

import (
"log"

"github.com/kubernetes/dashboard/resource/common"
"github.com/kubernetes/dashboard/resource/replicaset"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
)

type RollingUpdateStrategy struct {
MaxSurge int `json:"maxSurge"`
MaxUnavailable int `json:"maxUnavailable"`
}

type StatusInfo struct {
// Total number of desired replicas on the deployment
Replicas int `json:"replicas"`

// Number of non-terminated pods that have the desired template spec
Updated int `json:"updated"`

// Number of available pods (ready for at least minReadySeconds)
// targeted by this deployment
Available int `json:"available"`

// Total number of unavailable pods targeted by this deployment.
Unavailable int `json:"unavailable"`
}

// ReplicaSetDetail is a presentation layer view of Kubernetes Replica Set resource. This means
type DeploymentDetail struct {
ObjectMeta common.ObjectMeta `json:"objectMeta"`
TypeMeta common.TypeMeta `json:"typeMeta"`

// Label selector of the service.
Selector map[string]string `json:"selector"`

// Status information on the deployment
StatusInfo `json:"statusInfo"`

// The deployment strategy to use to replace existing pods with new ones.
// Valid options: Recreate, RollingUpdate
Strategy string `json:"strategy"`

// Min ready seconds
MinReadySeconds int `json:"minReadySeconds"`

// Rolling update strategy containing maxSurge and maxUnavailable
RollingUpdateStrategy `json:"rollingUpdateStrategy,omitempty"`

// RepliaSetList containing old replica sets from the deployment
OldReplicaSetList replicaset.ReplicaSetList `json:"oldReplicaSetList"`

// New replica set used by this deployment
NewReplicaSet replicaset.ReplicaSet `json:"newReplicaSet"`

// List of events related to this Deployment
EventList common.EventList `json:"eventList"`
}

func GetDeploymentDetail(client client.Interface, namespace string,
name string) (*DeploymentDetail, error) {

log.Printf("Getting details of %s deployment in %s namespace", name, namespace)

deploymentData, err := client.Extensions().Deployments(namespace).Get(name)
if err != nil {
return nil, err
}

channels := &common.ResourceChannels{
ReplicaSetList: common.GetReplicaSetListChannel(client.Extensions(), 1),
PodList: common.GetPodListChannel(client, 1),
}

replicaSetList := <-channels.ReplicaSetList.List
if err := <-channels.ReplicaSetList.Error; err != nil {
return nil, err
}

pods := <-channels.PodList.List
if err := <-channels.PodList.Error; err != nil {
return nil, err
}

oldReplicaSets, _, err := deploymentutil.FindOldReplicaSets(
deploymentData, replicaSetList.Items, pods)
if err != nil {
return nil, err
}

newReplicaSet, err := deploymentutil.FindNewReplicaSet(deploymentData, replicaSetList.Items)
if err != nil {
return nil, err
}

events, err := GetDeploymentEvents(client, namespace, name)
if err != nil {
return nil, err
}

return getDeploymentDetail(deploymentData, oldReplicaSets, newReplicaSet,
pods.Items, events), nil
}

func getDeploymentDetail(deployment *extensions.Deployment,
oldRs []*extensions.ReplicaSet, newRs *extensions.ReplicaSet,
pods []api.Pod, events *common.EventList) *DeploymentDetail {

newRsPodInfo := common.GetPodInfo(newRs.Status.Replicas, newRs.Spec.Replicas, pods)
newReplicaSet := replicaset.ToReplicaSet(newRs, &newRsPodInfo)

oldReplicaSets := make([]extensions.ReplicaSet, len(oldRs))
for i, replicaSet := range oldRs {
oldReplicaSets[i] = *replicaSet
}
oldReplicaSetList := replicaset.ToReplicaSetList(oldReplicaSets,
[]api.Service{}, pods, []api.Event{}, []api.Node{})

return &DeploymentDetail{
ObjectMeta: common.NewObjectMeta(deployment.ObjectMeta),
TypeMeta: common.NewTypeMeta(common.ResourceKindDeployment),
Selector: deployment.Spec.Selector.MatchLabels,
StatusInfo: GetStatusInfo(&deployment.Status),
Strategy: string(deployment.Spec.Strategy.Type),
MinReadySeconds: deployment.Spec.MinReadySeconds,
RollingUpdateStrategy: RollingUpdateStrategy{
MaxSurge: deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue(),
MaxUnavailable: deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue(),
},
OldReplicaSetList: *oldReplicaSetList,
NewReplicaSet: newReplicaSet,
EventList: *events,
}
}

func GetStatusInfo(deploymentStatus *extensions.DeploymentStatus) StatusInfo {
return StatusInfo{
Replicas: deploymentStatus.Replicas,
Updated: deploymentStatus.UpdatedReplicas,
Available: deploymentStatus.AvailableReplicas,
Unavailable: deploymentStatus.UnavailableReplicas,
}
}
35 changes: 35 additions & 0 deletions src/app/backend/resource/deployment/deploymentevents.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package deployment

import (
"log"

"github.com/kubernetes/dashboard/resource/common"
"github.com/kubernetes/dashboard/resource/event"
client "k8s.io/kubernetes/pkg/client/unversioned"
)

func GetDeploymentEvents(client client.Interface, namespace string, deploymentName string) (*common.EventList, error) {

log.Printf("Getting events related to %s deployment in %s namespace", deploymentName,
namespace)

// Get events for deployment.
dpEvents, err := event.GetEvents(client, namespace, deploymentName)
if err != nil {
return nil, err
}

if !event.IsTypeFilled(dpEvents) {
dpEvents = event.FillEventsType(dpEvents)
}

events := event.AppendEvents(dpEvents, common.EventList{
Namespace: namespace,
Events: make([]common.Event, 0),
})

log.Printf("Found %d events related to %s deployment in %s namespace",
len(events.Events), deploymentName, namespace)

return &events, nil
}
21 changes: 12 additions & 9 deletions src/app/backend/resource/replicaset/replicasetlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ func GetReplicaSetListFromChannels(channels *common.ResourceChannels) (
return nil, err
}

return getReplicaSetList(replicaSets.Items, services.Items, pods.Items, events.Items,
return ToReplicaSetList(replicaSets.Items, services.Items, pods.Items, events.Items,
nodes.Items), nil
}

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

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

replicaSetList.ReplicaSets = append(replicaSetList.ReplicaSets,
ReplicaSet{
ObjectMeta: common.NewObjectMeta(replicaSet.ObjectMeta),
TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet),
ContainerImages: common.GetContainerImages(&replicaSet.Spec.Template.Spec),
Pods: podInfo,
})
replicaSetList.ReplicaSets = append(replicaSetList.ReplicaSets, ToReplicaSet(&replicaSet, &podInfo))
}

return replicaSetList
}

func ToReplicaSet(replicaSet *extensions.ReplicaSet, podInfo *common.PodInfo) ReplicaSet {
return ReplicaSet{
ObjectMeta: common.NewObjectMeta(replicaSet.ObjectMeta),
TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet),
ContainerImages: common.GetContainerImages(&replicaSet.Spec.Template.Spec),
Pods: *podInfo,
}
}
139 changes: 139 additions & 0 deletions src/test/backend/resource/deployment/deploymentdetail_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package deployment

import (
"reflect"
"testing"

"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
"k8s.io/kubernetes/pkg/util/intstr"

"github.com/kubernetes/dashboard/resource/common"
"github.com/kubernetes/dashboard/resource/replicaset"
)

func TestGetDeploymentDetail(t *testing.T) {
podList := &api.PodList{}
eventList := &api.EventList{}

deployment := &extensions.Deployment{
ObjectMeta: api.ObjectMeta{
Name: "test-name",
Labels: map[string]string{"track": "beta"},
},
Spec: extensions.DeploymentSpec{
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
Replicas: 4,
MinReadySeconds: 5,
Strategy: extensions.DeploymentStrategy{
Type: extensions.RollingUpdateDeploymentStrategyType,
RollingUpdate: &extensions.RollingUpdateDeployment{
MaxSurge: intstr.FromInt(1),
MaxUnavailable: intstr.FromString("1"),
},
},
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Name: "test-pod-name",
Labels: map[string]string{"track": "beta"},
},
},
},
Status: extensions.DeploymentStatus{
Replicas: 4,
UpdatedReplicas: 2,
AvailableReplicas: 3,
UnavailableReplicas: 1,
},
}

podTemplateSpec := deploymentutil.GetNewReplicaSetTemplate(deployment)

newReplicaSet := extensions.ReplicaSet{
ObjectMeta: api.ObjectMeta{Name: "replica-set-1"},
Spec: extensions.ReplicaSetSpec{
Template: podTemplateSpec,
},
}

replicaSetList := &extensions.ReplicaSetList{
Items: []extensions.ReplicaSet{
newReplicaSet,
{
ObjectMeta: api.ObjectMeta{Name: "replica-set-2"},
},
},
}

cases := []struct {
namespace, name string
expectedActions []string
deployment *extensions.Deployment
expected *DeploymentDetail
}{
{
"test-namespace", "test-name",
[]string{"get", "list", "list", "list"},
deployment,
&DeploymentDetail{
ObjectMeta: common.ObjectMeta{
Name: "test-name",
Labels: map[string]string{"track": "beta"},
},
TypeMeta: common.TypeMeta{Kind: common.ResourceKindDeployment},
Selector: map[string]string{"foo": "bar"},
StatusInfo: StatusInfo{
Replicas: 4,
Updated: 2,
Available: 3,
Unavailable: 1,
},
Strategy: "RollingUpdate",
MinReadySeconds: 5,
RollingUpdateStrategy: RollingUpdateStrategy{
MaxSurge: 1,
MaxUnavailable: 1,
},
OldReplicaSetList: replicaset.ReplicaSetList{ReplicaSets: []replicaset.ReplicaSet{}},
NewReplicaSet: replicaset.ReplicaSet{
ObjectMeta: common.NewObjectMeta(newReplicaSet.ObjectMeta),
TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet),
Pods: common.PodInfo{Warnings: []common.Event{}},
},
EventList: common.EventList{
Namespace: "test-namespace",
Events: []common.Event{},
},
},
},
}

for _, c := range cases {

fakeClient := testclient.NewSimpleFake(c.deployment, replicaSetList, podList, eventList)

actual, _ := GetDeploymentDetail(fakeClient, c.namespace, c.name)

actions := fakeClient.Actions()
if len(actions) != len(c.expectedActions) {
t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions,
len(c.expectedActions), len(actions))
continue
}

for i, verb := range c.expectedActions {
if actions[i].GetVerb() != verb {
t.Errorf("Unexpected action: %+v, expected %s",
actions[i], verb)
}
}

if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("GetDeploymentDetail(client, namespace, name) == \ngot: %#v, \nexpected %#v",
actual, c.expected)
}
}
}
Loading