From d6f162caf306554ce916dd854943a8d273d9c7b8 Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Wed, 18 May 2016 16:19:15 -0300 Subject: [PATCH 1/9] Initial implementation of deployment detail --- .../resource/deployment/deploymentdetail.go | 71 ++++++++++++++ .../deployment/deploymentdetail_test.go | 93 +++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/app/backend/resource/deployment/deploymentdetail.go create mode 100644 src/test/backend/resource/deployment/deploymentdetail_test.go diff --git a/src/app/backend/resource/deployment/deploymentdetail.go b/src/app/backend/resource/deployment/deploymentdetail.go new file mode 100644 index 000000000000..55bc710425d8 --- /dev/null +++ b/src/app/backend/resource/deployment/deploymentdetail.go @@ -0,0 +1,71 @@ +package deployment + +import ( + "log" + + "github.com/kubernetes/dashboard/resource/common" + + "k8s.io/kubernetes/pkg/apis/extensions" + client "k8s.io/kubernetes/pkg/client/unversioned" +) + +type RollingUpdateStrategy struct { + MaxSurge int `json:"maxSurge"` + MaxUnavailable int `json:"maxUnavailable"` +} + +// 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 + Status extensions.DeploymentStatus `json:"status"` + + // 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 + // 1 max unavailable, 1 max surge + RollingUpdateStrategy `json:"rollingUpdateStrategy,omitempty"` + + //OldReplicaSets + + //NewReplicaSet + + //Events +} + +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 + } + + return getDeploymentDetail(deploymentData), nil +} + +func getDeploymentDetail(deployment *extensions.Deployment) *DeploymentDetail { + + return &DeploymentDetail{ + ObjectMeta: common.NewObjectMeta(deployment.ObjectMeta), + TypeMeta: common.NewTypeMeta(common.ResourceKindDeployment), + Selector: deployment.Spec.Selector.MatchLabels, + Status: 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(), + }, + } +} diff --git a/src/test/backend/resource/deployment/deploymentdetail_test.go b/src/test/backend/resource/deployment/deploymentdetail_test.go new file mode 100644 index 000000000000..8818a804f061 --- /dev/null +++ b/src/test/backend/resource/deployment/deploymentdetail_test.go @@ -0,0 +1,93 @@ +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" + "k8s.io/kubernetes/pkg/util/intstr" + + "github.com/kubernetes/dashboard/resource/common" +) + +func TestGetDeploymentDetailFromChannels(t *testing.T) { + + cases := []struct { + namespace, name string + expectedActions []string + deployment *extensions.Deployment + expected *DeploymentDetail + }{ + { + "test-namespace", "test-name", + []string{"get"}, + &extensions.Deployment{ + ObjectMeta: api.ObjectMeta{Name: "test-name"}, + 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"), + }, + }, + }, + Status: extensions.DeploymentStatus{ + Replicas: 4, + UpdatedReplicas: 2, + AvailableReplicas: 3, + UnavailableReplicas: 1, + }, + }, + &DeploymentDetail{ + ObjectMeta: common.ObjectMeta{Name: "test-name"}, + TypeMeta: common.TypeMeta{Kind: common.ResourceKindDeployment}, + Selector: map[string]string{"foo": "bar"}, + Status: extensions.DeploymentStatus{ + Replicas: 4, + UpdatedReplicas: 2, + AvailableReplicas: 3, + UnavailableReplicas: 1, + }, + Strategy: "RollingUpdate", + MinReadySeconds: 5, + RollingUpdateStrategy: RollingUpdateStrategy{ + MaxSurge: 1, + MaxUnavailable: 1, + }, + }, + }, + } + + for _, c := range cases { + + fakeClient := testclient.NewSimpleFake(c.deployment) + + 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", + c.namespace, c.name, actual, c.expected) + } + } +} From b2ebb59fb93fdb4c2bfc8fa2d84dd42fdf0d4115 Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Wed, 18 May 2016 16:23:01 -0300 Subject: [PATCH 2/9] Adding deployment detail api handler --- src/app/backend/handler/apihandler.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/app/backend/handler/apihandler.go b/src/app/backend/handler/apihandler.go index dd2402f30a60..d4d11b6b10a0 100644 --- a/src/app/backend/handler/apihandler.go +++ b/src/app/backend/handler/apihandler.go @@ -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). @@ -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) { From 0ba3eb6f8db0ec9d73daf744fb1c8216e62a82df Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Thu, 19 May 2016 17:20:02 -0300 Subject: [PATCH 3/9] Adding old replica sets --- .../resource/deployment/deploymentdetail.go | 50 +++++++++-- .../deployment/deploymentdetail_test.go | 90 +++++++++++++------ 2 files changed, 105 insertions(+), 35 deletions(-) diff --git a/src/app/backend/resource/deployment/deploymentdetail.go b/src/app/backend/resource/deployment/deploymentdetail.go index 55bc710425d8..52bf906ff6c9 100644 --- a/src/app/backend/resource/deployment/deploymentdetail.go +++ b/src/app/backend/resource/deployment/deploymentdetail.go @@ -4,9 +4,9 @@ import ( "log" "github.com/kubernetes/dashboard/resource/common" - "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" + deploymentutil "k8s.io/kubernetes/pkg/util/deployment" ) type RollingUpdateStrategy struct { @@ -31,15 +31,17 @@ type DeploymentDetail struct { // Min ready seconds MinReadySeconds int `json:"minReadySeconds"` - // Rolling update strategy - // 1 max unavailable, 1 max surge + // Rolling update strategy containing maxSurge and maxUnavailable RollingUpdateStrategy `json:"rollingUpdateStrategy,omitempty"` - //OldReplicaSets + // OldReplicaSets + OldReplicaSets []extensions.ReplicaSet `json:"oldReplicaSets"` - //NewReplicaSet + // NewReplicaSet + NewReplicaSet extensions.ReplicaSet `json:"newReplicaSet"` - //Events + // Events + // TODO } func GetDeploymentDetail(client client.Interface, namespace string, name string) (*DeploymentDetail, error) { @@ -51,10 +53,40 @@ func GetDeploymentDetail(client client.Interface, namespace string, name string) return nil, err } - return getDeploymentDetail(deploymentData), nil + 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 + //} + + return getDeploymentDetail(deploymentData, oldReplicaSets), nil } -func getDeploymentDetail(deployment *extensions.Deployment) *DeploymentDetail { +func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.ReplicaSet) *DeploymentDetail { + + oldReplicaSets := make([]extensions.ReplicaSet, len(old)) + for i, replicaSet := range old { + oldReplicaSets[i] = *replicaSet + } return &DeploymentDetail{ ObjectMeta: common.NewObjectMeta(deployment.ObjectMeta), @@ -67,5 +99,7 @@ func getDeploymentDetail(deployment *extensions.Deployment) *DeploymentDetail { MaxSurge: deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue(), MaxUnavailable: deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue(), }, + OldReplicaSets: oldReplicaSets, + //NewReplicaSet: *newReplicaSet, } } diff --git a/src/test/backend/resource/deployment/deploymentdetail_test.go b/src/test/backend/resource/deployment/deploymentdetail_test.go index 8818a804f061..8742987603e9 100644 --- a/src/test/backend/resource/deployment/deploymentdetail_test.go +++ b/src/test/backend/resource/deployment/deploymentdetail_test.go @@ -8,12 +8,63 @@ import ( "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" ) -func TestGetDeploymentDetailFromChannels(t *testing.T) { +func TestGetDeploymentDetail(t *testing.T) { + podList := &api.PodList{} + + 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 @@ -23,32 +74,15 @@ func TestGetDeploymentDetailFromChannels(t *testing.T) { }{ { "test-namespace", "test-name", - []string{"get"}, - &extensions.Deployment{ - ObjectMeta: api.ObjectMeta{Name: "test-name"}, - 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"), - }, - }, - }, - Status: extensions.DeploymentStatus{ - Replicas: 4, - UpdatedReplicas: 2, - AvailableReplicas: 3, - UnavailableReplicas: 1, - }, - }, + []string{"get", "list", "list"}, + deployment, &DeploymentDetail{ - ObjectMeta: common.ObjectMeta{Name: "test-name"}, - TypeMeta: common.TypeMeta{Kind: common.ResourceKindDeployment}, - Selector: map[string]string{"foo": "bar"}, + ObjectMeta: common.ObjectMeta{ + Name: "test-name", + Labels: map[string]string{"track": "beta"}, + }, + TypeMeta: common.TypeMeta{Kind: common.ResourceKindDeployment}, + Selector: map[string]string{"foo": "bar"}, Status: extensions.DeploymentStatus{ Replicas: 4, UpdatedReplicas: 2, @@ -61,13 +95,15 @@ func TestGetDeploymentDetailFromChannels(t *testing.T) { MaxSurge: 1, MaxUnavailable: 1, }, + OldReplicaSets: []extensions.ReplicaSet{}, + //NewReplicaSet: newReplicaSet, }, }, } for _, c := range cases { - fakeClient := testclient.NewSimpleFake(c.deployment) + fakeClient := testclient.NewSimpleFake(c.deployment, replicaSetList, podList) actual, _ := GetDeploymentDetail(fakeClient, c.namespace, c.name) From 4c5c5b233f83a59bf66fde43273d1b972d89362d Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Thu, 19 May 2016 17:41:20 -0300 Subject: [PATCH 4/9] Adding new replica set --- .../resource/deployment/deploymentdetail.go | 14 +++++++------- .../resource/deployment/deploymentdetail_test.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/backend/resource/deployment/deploymentdetail.go b/src/app/backend/resource/deployment/deploymentdetail.go index 52bf906ff6c9..888dc6776f2f 100644 --- a/src/app/backend/resource/deployment/deploymentdetail.go +++ b/src/app/backend/resource/deployment/deploymentdetail.go @@ -73,15 +73,15 @@ func GetDeploymentDetail(client client.Interface, namespace string, name string) return nil, err } - //newReplicaSet, err := deploymentutil.FindNewReplicaSet(deploymentData, replicaSetList.Items) - //if err != nil { - //return nil, err - //} + newReplicaSet, err := deploymentutil.FindNewReplicaSet(deploymentData, replicaSetList.Items) + if err != nil { + return nil, err + } - return getDeploymentDetail(deploymentData, oldReplicaSets), nil + return getDeploymentDetail(deploymentData, oldReplicaSets, newReplicaSet), nil } -func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.ReplicaSet) *DeploymentDetail { +func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.ReplicaSet, newReplicaSet *extensions.ReplicaSet) *DeploymentDetail { oldReplicaSets := make([]extensions.ReplicaSet, len(old)) for i, replicaSet := range old { @@ -100,6 +100,6 @@ func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.Re MaxUnavailable: deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue(), }, OldReplicaSets: oldReplicaSets, - //NewReplicaSet: *newReplicaSet, + NewReplicaSet: *newReplicaSet, } } diff --git a/src/test/backend/resource/deployment/deploymentdetail_test.go b/src/test/backend/resource/deployment/deploymentdetail_test.go index 8742987603e9..07e6f733a3a4 100644 --- a/src/test/backend/resource/deployment/deploymentdetail_test.go +++ b/src/test/backend/resource/deployment/deploymentdetail_test.go @@ -96,7 +96,7 @@ func TestGetDeploymentDetail(t *testing.T) { MaxUnavailable: 1, }, OldReplicaSets: []extensions.ReplicaSet{}, - //NewReplicaSet: newReplicaSet, + NewReplicaSet: newReplicaSet, }, }, } From 49d8ea82908d26a6a81424d15ead525d568c1584 Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Thu, 19 May 2016 18:21:14 -0300 Subject: [PATCH 5/9] Creating deployment events --- .../resource/deployment/deploymentevents.go | 35 ++++++++++ .../deployment/deploymentevents_test.go | 67 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/app/backend/resource/deployment/deploymentevents.go create mode 100644 src/test/backend/resource/deployment/deploymentevents_test.go diff --git a/src/app/backend/resource/deployment/deploymentevents.go b/src/app/backend/resource/deployment/deploymentevents.go new file mode 100644 index 000000000000..01c6047c8fee --- /dev/null +++ b/src/app/backend/resource/deployment/deploymentevents.go @@ -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 +} diff --git a/src/test/backend/resource/deployment/deploymentevents_test.go b/src/test/backend/resource/deployment/deploymentevents_test.go new file mode 100644 index 000000000000..8d1e06639bae --- /dev/null +++ b/src/test/backend/resource/deployment/deploymentevents_test.go @@ -0,0 +1,67 @@ +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" + + "github.com/kubernetes/dashboard/resource/common" +) + +func TestGetDeploymentEvents(t *testing.T) { + cases := []struct { + namespace, name string + eventList *api.EventList + deployment *extensions.Deployment + expectedActions []string + expected *common.EventList + }{ + { + "test-namespace", "test-name", + &api.EventList{Items: []api.Event{{Message: "test-message"}}}, + &extensions.Deployment{ + ObjectMeta: api.ObjectMeta{Name: "test-replicaset"}, + Spec: extensions.DeploymentSpec{ + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{}, + }}}, + []string{"list"}, + &common.EventList{ + Namespace: "test-namespace", + Events: []common.Event{{ + TypeMeta: common.TypeMeta{common.ResourceKindEvent}, + Message: "test-message", + Type: api.EventTypeNormal, + }}}, + }, + } + + for _, c := range cases { + fakeClient := testclient.NewSimpleFake(c.eventList, c.deployment) + + actual, _ := GetDeploymentEvents(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("GetDeploymentEvents(client,%#v, %#v) == \ngot: %#v, \nexpected %#v", + c.namespace, c.name, actual, c.expected) + } + } +} From 459221d297906548dfc4ddac23aa4805a99168ac Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Thu, 19 May 2016 18:21:30 -0300 Subject: [PATCH 6/9] Adding events to deployment details --- .../resource/deployment/deploymentdetail.go | 16 +++++++++++----- .../resource/deployment/deploymentdetail_test.go | 11 ++++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/app/backend/resource/deployment/deploymentdetail.go b/src/app/backend/resource/deployment/deploymentdetail.go index 888dc6776f2f..23cb4fa12f3a 100644 --- a/src/app/backend/resource/deployment/deploymentdetail.go +++ b/src/app/backend/resource/deployment/deploymentdetail.go @@ -37,11 +37,11 @@ type DeploymentDetail struct { // OldReplicaSets OldReplicaSets []extensions.ReplicaSet `json:"oldReplicaSets"` - // NewReplicaSet + // New replica set used by this deployment NewReplicaSet extensions.ReplicaSet `json:"newReplicaSet"` - // Events - // TODO + // List of events related to this Deployment + EventList common.EventList `json:"eventList"` } func GetDeploymentDetail(client client.Interface, namespace string, name string) (*DeploymentDetail, error) { @@ -78,10 +78,15 @@ func GetDeploymentDetail(client client.Interface, namespace string, name string) return nil, err } - return getDeploymentDetail(deploymentData, oldReplicaSets, newReplicaSet), nil + events, err := GetDeploymentEvents(client, namespace, name) + if err != nil { + return nil, err + } + + return getDeploymentDetail(deploymentData, oldReplicaSets, newReplicaSet, events), nil } -func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.ReplicaSet, newReplicaSet *extensions.ReplicaSet) *DeploymentDetail { +func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.ReplicaSet, newReplicaSet *extensions.ReplicaSet, events *common.EventList) *DeploymentDetail { oldReplicaSets := make([]extensions.ReplicaSet, len(old)) for i, replicaSet := range old { @@ -101,5 +106,6 @@ func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.Re }, OldReplicaSets: oldReplicaSets, NewReplicaSet: *newReplicaSet, + EventList: *events, } } diff --git a/src/test/backend/resource/deployment/deploymentdetail_test.go b/src/test/backend/resource/deployment/deploymentdetail_test.go index 07e6f733a3a4..d26712f2acdf 100644 --- a/src/test/backend/resource/deployment/deploymentdetail_test.go +++ b/src/test/backend/resource/deployment/deploymentdetail_test.go @@ -16,6 +16,7 @@ import ( func TestGetDeploymentDetail(t *testing.T) { podList := &api.PodList{} + eventList := &api.EventList{} deployment := &extensions.Deployment{ ObjectMeta: api.ObjectMeta{ @@ -74,7 +75,7 @@ func TestGetDeploymentDetail(t *testing.T) { }{ { "test-namespace", "test-name", - []string{"get", "list", "list"}, + []string{"get", "list", "list", "list"}, deployment, &DeploymentDetail{ ObjectMeta: common.ObjectMeta{ @@ -97,13 +98,17 @@ func TestGetDeploymentDetail(t *testing.T) { }, OldReplicaSets: []extensions.ReplicaSet{}, NewReplicaSet: newReplicaSet, + EventList: common.EventList{ + Namespace: "test-namespace", + Events: []common.Event{}, + }, }, }, } for _, c := range cases { - fakeClient := testclient.NewSimpleFake(c.deployment, replicaSetList, podList) + fakeClient := testclient.NewSimpleFake(c.deployment, replicaSetList, podList, eventList) actual, _ := GetDeploymentDetail(fakeClient, c.namespace, c.name) @@ -123,7 +128,7 @@ func TestGetDeploymentDetail(t *testing.T) { if !reflect.DeepEqual(actual, c.expected) { t.Errorf("GetDeploymentDetail(client, namespace, name) == \ngot: %#v, \nexpected %#v", - c.namespace, c.name, actual, c.expected) + actual, c.expected) } } } From 9828372d86fdc430b1c318b21c2261ad31566cff Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Fri, 20 May 2016 16:26:30 -0300 Subject: [PATCH 7/9] Using dashboard types --- .../resource/deployment/deploymentdetail.go | 63 +++++++++++++++---- .../deployment/deploymentdetail_test.go | 9 ++- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/app/backend/resource/deployment/deploymentdetail.go b/src/app/backend/resource/deployment/deploymentdetail.go index 23cb4fa12f3a..98e44b67a515 100644 --- a/src/app/backend/resource/deployment/deploymentdetail.go +++ b/src/app/backend/resource/deployment/deploymentdetail.go @@ -4,6 +4,8 @@ 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" @@ -25,7 +27,8 @@ type DeploymentDetail struct { // Status Status extensions.DeploymentStatus `json:"status"` - // The deployment strategy to use to replace existing pods with new ones. Valid options: Recreate, RollingUpdate + // The deployment strategy to use to replace existing pods with new ones. + // Valid options: Recreate, RollingUpdate Strategy string `json:"strategy"` // Min ready seconds @@ -34,17 +37,18 @@ type DeploymentDetail struct { // Rolling update strategy containing maxSurge and maxUnavailable RollingUpdateStrategy `json:"rollingUpdateStrategy,omitempty"` - // OldReplicaSets - OldReplicaSets []extensions.ReplicaSet `json:"oldReplicaSets"` + // RepliaSetList containing old replica sets from the deployment + OldReplicaSetList replicaset.ReplicaSetList `json:"oldReplicaSetList"` // New replica set used by this deployment - NewReplicaSet extensions.ReplicaSet `json:"newReplicaSet"` + 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) { +func GetDeploymentDetail(client client.Interface, namespace string, + name string) (*DeploymentDetail, error) { log.Printf("Getting details of %s deployment in %s namespace", name, namespace) @@ -68,7 +72,8 @@ func GetDeploymentDetail(client client.Interface, namespace string, name string) return nil, err } - oldReplicaSets, _, err := deploymentutil.FindOldReplicaSets(deploymentData, replicaSetList.Items, pods) + oldReplicaSets, _, err := deploymentutil.FindOldReplicaSets( + deploymentData, replicaSetList.Items, pods) if err != nil { return nil, err } @@ -83,15 +88,22 @@ func GetDeploymentDetail(client client.Interface, namespace string, name string) return nil, err } - return getDeploymentDetail(deploymentData, oldReplicaSets, newReplicaSet, events), nil + return getDeploymentDetail(deploymentData, oldReplicaSets, newReplicaSet, + pods.Items, events), nil } -func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.ReplicaSet, newReplicaSet *extensions.ReplicaSet, events *common.EventList) *DeploymentDetail { +func getDeploymentDetail(deployment *extensions.Deployment, + oldRs []*extensions.ReplicaSet, newRs *extensions.ReplicaSet, + pods []api.Pod, events *common.EventList) *DeploymentDetail { - oldReplicaSets := make([]extensions.ReplicaSet, len(old)) - for i, replicaSet := range old { + newRsPodInfo := common.GetPodInfo(newRs.Status.Replicas, newRs.Spec.Replicas, pods) + newReplicaSet := toReplicaSet(newRs, &newRsPodInfo) + + oldReplicaSets := make([]extensions.ReplicaSet, len(oldRs)) + for i, replicaSet := range oldRs { oldReplicaSets[i] = *replicaSet } + oldReplicaSetList := toReplicaSetList(oldReplicaSets, pods) return &DeploymentDetail{ ObjectMeta: common.NewObjectMeta(deployment.ObjectMeta), @@ -104,8 +116,33 @@ func getDeploymentDetail(deployment *extensions.Deployment, old []*extensions.Re MaxSurge: deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue(), MaxUnavailable: deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue(), }, - OldReplicaSets: oldReplicaSets, - NewReplicaSet: *newReplicaSet, - EventList: *events, + OldReplicaSetList: oldReplicaSetList, + NewReplicaSet: newReplicaSet, + EventList: *events, + } +} + +func toReplicaSetList(resourceList []extensions.ReplicaSet, pods []api.Pod) replicaset.ReplicaSetList { + replicaSetList := replicaset.ReplicaSetList{ + ReplicaSets: make([]replicaset.ReplicaSet, 0), + } + + for _, replicaSet := range resourceList { + matchingPods := common.FilterNamespacedPodsBySelector(pods, replicaSet.ObjectMeta.Namespace, + replicaSet.Spec.Selector.MatchLabels) + podInfo := common.GetPodInfo(replicaSet.Status.Replicas, replicaSet.Spec.Replicas, matchingPods) + + replicaSetList.ReplicaSets = append(replicaSetList.ReplicaSets, toReplicaSet(&replicaSet, &podInfo)) + } + + return replicaSetList +} + +func toReplicaSet(replicaSet *extensions.ReplicaSet, podInfo *common.PodInfo) replicaset.ReplicaSet { + return replicaset.ReplicaSet{ + ObjectMeta: common.NewObjectMeta(replicaSet.ObjectMeta), + TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet), + ContainerImages: common.GetContainerImages(&replicaSet.Spec.Template.Spec), + Pods: *podInfo, } } diff --git a/src/test/backend/resource/deployment/deploymentdetail_test.go b/src/test/backend/resource/deployment/deploymentdetail_test.go index d26712f2acdf..d8ea94acdb2f 100644 --- a/src/test/backend/resource/deployment/deploymentdetail_test.go +++ b/src/test/backend/resource/deployment/deploymentdetail_test.go @@ -12,6 +12,7 @@ import ( "k8s.io/kubernetes/pkg/util/intstr" "github.com/kubernetes/dashboard/resource/common" + "github.com/kubernetes/dashboard/resource/replicaset" ) func TestGetDeploymentDetail(t *testing.T) { @@ -96,8 +97,12 @@ func TestGetDeploymentDetail(t *testing.T) { MaxSurge: 1, MaxUnavailable: 1, }, - OldReplicaSets: []extensions.ReplicaSet{}, - NewReplicaSet: newReplicaSet, + 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{}, From b0dc867e41b2b1a84177251a9d8de56ed8729a5f Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Fri, 20 May 2016 16:48:21 -0300 Subject: [PATCH 8/9] Introducing deployment status info --- .../resource/deployment/deploymentdetail.go | 30 +++++++++++++++++-- .../deployment/deploymentdetail_test.go | 10 +++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/app/backend/resource/deployment/deploymentdetail.go b/src/app/backend/resource/deployment/deploymentdetail.go index 98e44b67a515..19d8e57ab5a9 100644 --- a/src/app/backend/resource/deployment/deploymentdetail.go +++ b/src/app/backend/resource/deployment/deploymentdetail.go @@ -16,6 +16,21 @@ type RollingUpdateStrategy struct { 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"` @@ -24,8 +39,8 @@ type DeploymentDetail struct { // Label selector of the service. Selector map[string]string `json:"selector"` - // Status - Status extensions.DeploymentStatus `json:"status"` + // Status information on the deployment + StatusInfo `json:"status"` // The deployment strategy to use to replace existing pods with new ones. // Valid options: Recreate, RollingUpdate @@ -109,7 +124,7 @@ func getDeploymentDetail(deployment *extensions.Deployment, ObjectMeta: common.NewObjectMeta(deployment.ObjectMeta), TypeMeta: common.NewTypeMeta(common.ResourceKindDeployment), Selector: deployment.Spec.Selector.MatchLabels, - Status: deployment.Status, + StatusInfo: GetStatusInfo(&deployment.Status), Strategy: string(deployment.Spec.Strategy.Type), MinReadySeconds: deployment.Spec.MinReadySeconds, RollingUpdateStrategy: RollingUpdateStrategy{ @@ -146,3 +161,12 @@ func toReplicaSet(replicaSet *extensions.ReplicaSet, podInfo *common.PodInfo) re Pods: *podInfo, } } + +func GetStatusInfo(deploymentStatus *extensions.DeploymentStatus) StatusInfo { + return StatusInfo{ + Replicas: deploymentStatus.Replicas, + Updated: deploymentStatus.UpdatedReplicas, + Available: deploymentStatus.AvailableReplicas, + Unavailable: deploymentStatus.UnavailableReplicas, + } +} diff --git a/src/test/backend/resource/deployment/deploymentdetail_test.go b/src/test/backend/resource/deployment/deploymentdetail_test.go index d8ea94acdb2f..780950228c7b 100644 --- a/src/test/backend/resource/deployment/deploymentdetail_test.go +++ b/src/test/backend/resource/deployment/deploymentdetail_test.go @@ -85,11 +85,11 @@ func TestGetDeploymentDetail(t *testing.T) { }, TypeMeta: common.TypeMeta{Kind: common.ResourceKindDeployment}, Selector: map[string]string{"foo": "bar"}, - Status: extensions.DeploymentStatus{ - Replicas: 4, - UpdatedReplicas: 2, - AvailableReplicas: 3, - UnavailableReplicas: 1, + StatusInfo: StatusInfo{ + Replicas: 4, + Updated: 2, + Available: 3, + Unavailable: 1, }, Strategy: "RollingUpdate", MinReadySeconds: 5, From b0758e7038cf4c175bcbe4754b060e86a83af785 Mon Sep 17 00:00:00 2001 From: "Luiz Felipe G. Pereira" Date: Mon, 23 May 2016 06:50:46 -0300 Subject: [PATCH 9/9] Removing duplication with replicaset --- .../resource/deployment/deploymentdetail.go | 34 +++---------------- .../resource/replicaset/replicasetlist.go | 21 +++++++----- 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/src/app/backend/resource/deployment/deploymentdetail.go b/src/app/backend/resource/deployment/deploymentdetail.go index 19d8e57ab5a9..997f96634ac8 100644 --- a/src/app/backend/resource/deployment/deploymentdetail.go +++ b/src/app/backend/resource/deployment/deploymentdetail.go @@ -40,7 +40,7 @@ type DeploymentDetail struct { Selector map[string]string `json:"selector"` // Status information on the deployment - StatusInfo `json:"status"` + StatusInfo `json:"statusInfo"` // The deployment strategy to use to replace existing pods with new ones. // Valid options: Recreate, RollingUpdate @@ -112,13 +112,14 @@ func getDeploymentDetail(deployment *extensions.Deployment, pods []api.Pod, events *common.EventList) *DeploymentDetail { newRsPodInfo := common.GetPodInfo(newRs.Status.Replicas, newRs.Spec.Replicas, pods) - newReplicaSet := toReplicaSet(newRs, &newRsPodInfo) + newReplicaSet := replicaset.ToReplicaSet(newRs, &newRsPodInfo) oldReplicaSets := make([]extensions.ReplicaSet, len(oldRs)) for i, replicaSet := range oldRs { oldReplicaSets[i] = *replicaSet } - oldReplicaSetList := toReplicaSetList(oldReplicaSets, pods) + oldReplicaSetList := replicaset.ToReplicaSetList(oldReplicaSets, + []api.Service{}, pods, []api.Event{}, []api.Node{}) return &DeploymentDetail{ ObjectMeta: common.NewObjectMeta(deployment.ObjectMeta), @@ -131,37 +132,12 @@ func getDeploymentDetail(deployment *extensions.Deployment, MaxSurge: deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue(), MaxUnavailable: deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue(), }, - OldReplicaSetList: oldReplicaSetList, + OldReplicaSetList: *oldReplicaSetList, NewReplicaSet: newReplicaSet, EventList: *events, } } -func toReplicaSetList(resourceList []extensions.ReplicaSet, pods []api.Pod) replicaset.ReplicaSetList { - replicaSetList := replicaset.ReplicaSetList{ - ReplicaSets: make([]replicaset.ReplicaSet, 0), - } - - for _, replicaSet := range resourceList { - matchingPods := common.FilterNamespacedPodsBySelector(pods, replicaSet.ObjectMeta.Namespace, - replicaSet.Spec.Selector.MatchLabels) - podInfo := common.GetPodInfo(replicaSet.Status.Replicas, replicaSet.Spec.Replicas, matchingPods) - - replicaSetList.ReplicaSets = append(replicaSetList.ReplicaSets, toReplicaSet(&replicaSet, &podInfo)) - } - - return replicaSetList -} - -func toReplicaSet(replicaSet *extensions.ReplicaSet, podInfo *common.PodInfo) replicaset.ReplicaSet { - return replicaset.ReplicaSet{ - ObjectMeta: common.NewObjectMeta(replicaSet.ObjectMeta), - TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet), - ContainerImages: common.GetContainerImages(&replicaSet.Spec.Template.Spec), - Pods: *podInfo, - } -} - func GetStatusInfo(deploymentStatus *extensions.DeploymentStatus) StatusInfo { return StatusInfo{ Replicas: deploymentStatus.Replicas, diff --git a/src/app/backend/resource/replicaset/replicasetlist.go b/src/app/backend/resource/replicaset/replicasetlist.go index fc6922f20af4..f55476939ade 100644 --- a/src/app/backend/resource/replicaset/replicasetlist.go +++ b/src/app/backend/resource/replicaset/replicasetlist.go @@ -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 { @@ -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, + } +}