diff --git a/src/app/backend/deploy.go b/src/app/backend/deploy.go
index d7b5dd2b68fc..7f36523ffeac 100644
--- a/src/app/backend/deploy.go
+++ b/src/app/backend/deploy.go
@@ -159,7 +159,7 @@ func DeployApp(spec *AppDeploymentSpec, client client.Interface) error {
Containers: []api.Container{containerSpec},
}
if spec.ImagePullSecret != nil {
- podSpec.ImagePullSecrets = []api.LocalObjectReference{api.LocalObjectReference{Name: *spec.ImagePullSecret}}
+ podSpec.ImagePullSecrets = []api.LocalObjectReference{{Name: *spec.ImagePullSecret}}
}
podTemplate := &api.PodTemplateSpec{
diff --git a/src/app/backend/events.go b/src/app/backend/events.go
index 226701da5f90..88a64e226b56 100644
--- a/src/app/backend/events.go
+++ b/src/app/backend/events.go
@@ -137,19 +137,22 @@ func GetReplicationControllerPodsEvents(client *client.Client, namespace, replic
return nil, err
}
- events := make([]api.Event, 0, 0)
+ events, err := GetPodsEvents(client, pods)
- for _, pod := range pods.Items {
- fieldSelector, err := fields.ParseSelector("involvedObject.name=" + pod.Name)
+ if err != nil {
+ return nil, err
+ }
- if err != nil {
- return nil, err
- }
+ return events, nil
+}
- list, err := client.Events(namespace).List(unversioned.ListOptions{
- LabelSelector: unversioned.LabelSelector{labels.Everything()},
- FieldSelector: unversioned.FieldSelector{fieldSelector},
- })
+// Gets events associated to given list of pods
+// TODO(floreks): refactor this to make single API call instead of N api calls
+func GetPodsEvents(client *client.Client, pods *api.PodList) ([]api.Event, error) {
+ events := make([]api.Event, 0, 0)
+
+ for _, pod := range pods.Items {
+ list, err := GetPodEvents(client, pod)
if err != nil {
return nil, err
@@ -164,6 +167,26 @@ func GetReplicationControllerPodsEvents(client *client.Client, namespace, replic
return events, nil
}
+// Gets events associated to given pod
+func GetPodEvents(client client.Interface, pod api.Pod) (*api.EventList, error) {
+ fieldSelector, err := fields.ParseSelector("involvedObject.name=" + pod.Name)
+
+ if err != nil {
+ return nil, err
+ }
+
+ list, err := client.Events(pod.Namespace).List(unversioned.ListOptions{
+ LabelSelector: unversioned.LabelSelector{labels.Everything()},
+ FieldSelector: unversioned.FieldSelector{fieldSelector},
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ return list, nil
+}
+
// Appends events from source slice to target events representation.
func AppendEvents(source []api.Event, target Events) Events {
for _, event := range source {
diff --git a/src/app/backend/eventscommon.go b/src/app/backend/eventscommon.go
new file mode 100644
index 000000000000..4f185636e631
--- /dev/null
+++ b/src/app/backend/eventscommon.go
@@ -0,0 +1,152 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// 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 main
+
+import (
+ "k8s.io/kubernetes/pkg/api"
+ client "k8s.io/kubernetes/pkg/client/unversioned"
+ "log"
+ "strings"
+)
+
+// Partial string to correctly filter warning events.
+// Has to be lower case for correct case insensitive comparison.
+const FAILED_REASON_PARTIAL = "failed"
+
+// Contains basic information about event related to a pod
+type PodEvent struct {
+ // Short, machine understandable string that gives the reason
+ // for this event being generated.
+ Reason string `json:"reason"`
+
+ // A human-readable description of the status of related pod.
+ Message string `json:"message"`
+}
+
+// Returns warning pod events based on given list of pods.
+// TODO(floreks) : Import and use Set instead of custom function to get rid of duplicates
+func GetPodsEventWarnings(client client.Interface, pods []api.Pod) (result []PodEvent, err error) {
+ for _, pod := range pods {
+ if !isRunningOrSucceeded(pod) {
+ log.Printf("Getting warning events from pod: %s", pod.Name)
+ events, err := GetPodEvents(client, pod)
+
+ if err != nil {
+ return nil, err
+ }
+
+ result = getPodsEventWarnings(events)
+ }
+ }
+
+ return removeDuplicates(result), nil
+}
+
+// Returns list of Pod Event model objects based on kubernetes API event list object
+// Event list object is filtered to get only warning events.
+func getPodsEventWarnings(eventList *api.EventList) []PodEvent {
+ result := make([]PodEvent, 0)
+
+ var events []api.Event
+ if isTypeFilled(eventList.Items) {
+ events = filterEventsByType(eventList.Items, api.EventTypeWarning)
+ } else {
+ events = filterEventsByReason(eventList.Items, FAILED_REASON_PARTIAL)
+ }
+
+ for _, event := range events {
+ result = append(result, PodEvent{
+ Message: event.Message,
+ Reason: event.Reason,
+ })
+ }
+
+ return result
+}
+
+// Filters kubernetes API event objects based on event type.
+// Empty string will return all events.
+func filterEventsByType(events []api.Event, eventType string) []api.Event {
+ if len(eventType) == 0 || len(events) == 0 {
+ return events
+ }
+
+ result := make([]api.Event, 0)
+ for _, event := range events {
+ if event.Type == eventType {
+ result = append(result, event)
+ }
+ }
+
+ return result
+}
+
+// Filters kubernetes API event objects based on reason property.
+// Empty string will return all events.
+func filterEventsByReason(events []api.Event, partial string) []api.Event {
+ if len(partial) == 0 || len(events) == 0 {
+ return events
+ }
+
+ result := make([]api.Event, 0)
+ for _, event := range events {
+ if strings.Contains(strings.ToLower(event.Reason), partial) {
+ result = append(result, event)
+ }
+ }
+
+ return result
+}
+
+// Returns true if all given events type is filled, false otherwise.
+// This is needed as some older versions of kubernetes do not have Type property filled.
+func isTypeFilled(events []api.Event) bool {
+ if len(events) == 0 {
+ return false
+ }
+
+ for _, event := range events {
+ if len(event.Type) == 0 {
+ return false
+ }
+ }
+
+ return true
+}
+
+// Removes duplicate strings from the slice
+func removeDuplicates(slice []PodEvent) []PodEvent {
+ visited := make(map[string]bool, 0)
+ result := make([]PodEvent, 0)
+
+ for _, elem := range slice {
+ if !visited[elem.Reason] {
+ visited[elem.Reason] = true
+ result = append(result, elem)
+ }
+ }
+
+ return result
+}
+
+// Returns true if given pod is in state running or succeeded, false otherwise
+func isRunningOrSucceeded(pod api.Pod) bool {
+ switch pod.Status.Phase {
+ case api.PodRunning, api.PodSucceeded:
+ return true
+ }
+
+ return false
+}
diff --git a/src/app/backend/replicationcontrollercommon.go b/src/app/backend/replicationcontrollercommon.go
index 6c93fff22250..52ebc014534a 100644
--- a/src/app/backend/replicationcontrollercommon.go
+++ b/src/app/backend/replicationcontrollercommon.go
@@ -24,7 +24,7 @@ import (
type ReplicationControllerWithPods struct {
ReplicationController *api.ReplicationController
- Pods *api.PodList
+ Pods *api.PodList
}
// ReplicationControllerPodInfo represents aggregate information about replication controller pods.
@@ -43,6 +43,9 @@ type ReplicationControllerPodInfo struct {
// Number of pods that are failed.
Failed int `json:"failed"`
+
+ // Unique warning messages related to pods in this Replication Controller.
+ Warnings []PodEvent `json:"warnings"`
}
// Returns structure containing ReplicationController and Pods for the given replication controller.
@@ -66,7 +69,7 @@ func getRawReplicationControllerWithPods(client client.Interface, namespace, nam
replicationControllerAndPods := &ReplicationControllerWithPods{
ReplicationController: replicationController,
- Pods: pods,
+ Pods: pods,
}
return replicationControllerAndPods, nil
}
diff --git a/src/app/backend/replicationcontrollerlist.go b/src/app/backend/replicationcontrollerlist.go
index 98472bbda5a4..a7340534fe2c 100644
--- a/src/app/backend/replicationcontrollerlist.go
+++ b/src/app/backend/replicationcontrollerlist.go
@@ -24,6 +24,9 @@ import (
"k8s.io/kubernetes/pkg/labels"
)
+// Callback function in order to get the pod status errors
+type GetPodsEventWarningsFunc func(pods []api.Pod) ([]PodEvent, error)
+
// ReplicationControllerList contains a list of Replication Controllers in the cluster.
type ReplicationControllerList struct {
// Unordered list of Replication Controllers.
@@ -45,7 +48,7 @@ type ReplicationController struct {
// Label of this Replication Controller.
Labels map[string]string `json:"labels"`
- // Aggergate information about pods belonging to this repolica set.
+ // Aggregate information about pods belonging to this repolica set.
Pods ReplicationControllerPodInfo `json:"pods"`
// Container images of the Replication Controller.
@@ -88,14 +91,34 @@ func GetReplicationControllerList(client *client.Client) (*ReplicationController
return nil, err
}
- return getReplicationControllerList(replicationControllers.Items, services.Items, pods.Items), nil
+ // Anonymous callback function to get pods warnings.
+ // Function fulfils GetPodsEventWarningsFunc type contract.
+ // Based on list of api pods returns list of pod related warning events
+ getPodsEventWarningsFn := func(pods []api.Pod) ([]PodEvent, error) {
+ errors, err := GetPodsEventWarnings(client, pods)
+
+ if err != nil {
+ return nil, err
+ }
+
+ return errors, nil
+ }
+
+ result, err := getReplicationControllerList(replicationControllers.Items, services.Items, pods.Items, getPodsEventWarningsFn)
+
+ if err != nil {
+ return nil, err
+ }
+
+ return result, nil
}
// Returns a list of all Replication Controller model objects in the cluster, based on all Kubernetes
// Replication Controller and Service API objects.
// The function processes all Replication Controllers API objects and finds matching Services for them.
-func getReplicationControllerList(replicationControllers []api.ReplicationController, services []api.Service,
- pods []api.Pod) *ReplicationControllerList {
+func getReplicationControllerList(replicationControllers []api.ReplicationController,
+ services []api.Service, pods []api.Pod, getPodsEventWarningsFn GetPodsEventWarningsFunc) (
+ *ReplicationControllerList, error) {
replicationControllerList := &ReplicationControllerList{ReplicationControllers: make([]ReplicationController, 0)}
@@ -125,6 +148,13 @@ func getReplicationControllerList(replicationControllers []api.ReplicationContro
}
}
podInfo := getReplicationControllerPodInfo(&replicationController, matchingPods)
+ podErrors, err := getPodsEventWarningsFn(matchingPods)
+
+ if err != nil {
+ return nil, err
+ }
+
+ podInfo.Warnings = podErrors
replicationControllerList.ReplicationControllers = append(replicationControllerList.ReplicationControllers, ReplicationController{
Name: replicationController.ObjectMeta.Name,
@@ -139,7 +169,7 @@ func getReplicationControllerList(replicationControllers []api.ReplicationContro
})
}
- return replicationControllerList
+ return replicationControllerList, nil
}
// Returns all services that target the same Pods (or subset) as the given Replication Controller.
diff --git a/src/app/externs/backendapi.js b/src/app/externs/backendapi.js
index 5a48b351e781..2bc5ea4c7736 100644
--- a/src/app/externs/backendapi.js
+++ b/src/app/externs/backendapi.js
@@ -98,13 +98,22 @@ backendApi.Event;
*/
backendApi.ReplicationControllerList;
+/**
+ * @typedef {{
+ * reason: string,
+ * message: string
+ * }}
+ */
+backendApi.PodEvent;
+
/**
* @typedef {{
* current: number,
* desired: number,
* running: number,
* pending: number,
- * failed: number
+ * failed: number,
+ * warnings: !Array
* }}
*/
backendApi.ReplicationControllerPodInfo;
diff --git a/src/app/frontend/replicationcontrollerlist/replicationcontrollercard.html b/src/app/frontend/replicationcontrollerlist/replicationcontrollercard.html
index a4f31aabcac9..75f5f8b53f92 100644
--- a/src/app/frontend/replicationcontrollerlist/replicationcontrollercard.html
+++ b/src/app/frontend/replicationcontrollerlist/replicationcontrollercard.html
@@ -19,11 +19,23 @@
-
-
+
+
+ error
+ One or more pods have errors
+
+
+ timelapse
+ One or more pods are in pending state
+
+
+
+
+
+
+ {{::warning.message}}
+
+
+
diff --git a/src/app/frontend/replicationcontrollerlist/replicationcontrollercard.scss b/src/app/frontend/replicationcontrollerlist/replicationcontrollercard.scss
index 8d4b905007fb..caaf6f6c0841 100644
--- a/src/app/frontend/replicationcontrollerlist/replicationcontrollercard.scss
+++ b/src/app/frontend/replicationcontrollerlist/replicationcontrollercard.scss
@@ -29,6 +29,10 @@
}
}
+.kd-replicationcontroller-card-status-icon {
+ margin-left: $baseline-grid;
+}
+
.kd-replicationcontroller-card-title {
font-weight: $regular-font-weight;
margin: 0;
@@ -38,6 +42,12 @@
}
}
+.kd-replicationcontroller-card-error {
+ color: $secondary;
+ margin-top: $baseline-grid / 2;
+ word-wrap: break-word;
+}
+
.kd-replicationcontroller-card-description {
overflow: auto;
white-space: pre-wrap;
@@ -87,6 +97,6 @@
margin-right: 2 * $baseline-grid;
}
-.kd-replicase-card-pods-stat {
+.kd-replicationcontroller-card-pods-stat {
white-space: nowrap;
}
diff --git a/src/app/frontend/replicationcontrollerlist/replicationcontrollercard_controller.js b/src/app/frontend/replicationcontrollerlist/replicationcontrollercard_controller.js
index edf9d7ed2b1b..d4d4e12f6d62 100644
--- a/src/app/frontend/replicationcontrollerlist/replicationcontrollercard_controller.js
+++ b/src/app/frontend/replicationcontrollerlist/replicationcontrollercard_controller.js
@@ -67,4 +67,19 @@ export default class ReplicationControllerCardController {
areDesiredPodsRunning() {
return this.replicationController.pods.running === this.replicationController.pods.desired;
}
+
+ /**
+ * Returns true if any of replication controller pods has warning, false otherwise
+ * @return {boolean}
+ * @export
+ */
+ hasWarnings() { return this.replicationController.pods.warnings.length > 0; }
+
+ /**
+ * Returns true if replication controller pods have no warnings and there is at least one pod
+ * in pending state, false otherwise
+ * @return {boolean}
+ * @export
+ */
+ isPending() { return !this.hasWarnings() && this.replicationController.pods.pending > 0; }
}
diff --git a/src/test/backend/apihandler_test.go b/src/test/backend/apihandler_test.go
index e423723c97b9..83b57e8b276c 100644
--- a/src/test/backend/apihandler_test.go
+++ b/src/test/backend/apihandler_test.go
@@ -25,8 +25,8 @@ import (
func TestFormatRequestLog(t *testing.T) {
cases := []struct {
- request *restful.Request
- expected string
+ request *restful.Request
+ expected string
}{
{
&restful.Request{
@@ -40,7 +40,7 @@ func TestFormatRequestLog(t *testing.T) {
},
}
for _, c := range cases {
- actual := FormatRequestLog(c.request,)
+ actual := FormatRequestLog(c.request)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("FormatRequestLog(%#v) == %#v, expected %#v", c.request, actual, c.expected)
}
@@ -49,9 +49,9 @@ func TestFormatRequestLog(t *testing.T) {
func TestFormatResponseLog(t *testing.T) {
cases := []struct {
- response *restful.Response
- request *restful.Request
- expected string
+ response *restful.Response
+ request *restful.Request
+ expected string
}{
{
&restful.Response{},
diff --git a/src/test/backend/eventscommon_test.go b/src/test/backend/eventscommon_test.go
new file mode 100644
index 000000000000..f8990e289911
--- /dev/null
+++ b/src/test/backend/eventscommon_test.go
@@ -0,0 +1,341 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// 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 main
+
+import (
+ "reflect"
+ "testing"
+
+ "k8s.io/kubernetes/pkg/api"
+ "k8s.io/kubernetes/pkg/client/unversioned/testclient"
+)
+
+func TestGetPodsEventWarningsApi(t *testing.T) {
+ cases := []struct {
+ pods []api.Pod
+ expectedActions []string
+ }{
+ {nil, []string{}},
+ {
+ []api.Pod{
+ {
+ Status: api.PodStatus{
+ Phase: api.PodFailed,
+ },
+ },
+ },
+ []string{"get"},
+ },
+ {
+ []api.Pod{
+ {
+ Status: api.PodStatus{
+ Phase: api.PodRunning,
+ },
+ },
+ },
+ []string{},
+ },
+ }
+
+ for _, c := range cases {
+ eventList := &api.EventList{}
+ fakeClient := testclient.NewSimpleFake(eventList)
+
+ GetPodsEventWarnings(fakeClient, c.pods)
+
+ 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
+ }
+ }
+}
+
+func TestGetPodsEventWarnings(t *testing.T) {
+ cases := []struct {
+ events *api.EventList
+ expected []PodEvent
+ }{
+ {&api.EventList{Items: nil}, []PodEvent{}},
+ {
+ &api.EventList{
+ Items: []api.Event{
+ {
+ Message: "msg",
+ Reason: "reason",
+ Type: api.EventTypeWarning,
+ },
+ },
+ },
+ []PodEvent{
+ {
+ Message: "msg",
+ Reason: "reason",
+ },
+ },
+ },
+ {
+ &api.EventList{
+ Items: []api.Event{
+ {
+ Message: "msg",
+ Reason: "failed",
+ },
+ },
+ },
+ []PodEvent{
+ {
+ Message: "msg",
+ Reason: "failed",
+ },
+ },
+ },
+ {
+ &api.EventList{
+ Items: []api.Event{
+ {
+ Message: "msg",
+ Reason: "reason",
+ },
+ },
+ },
+ []PodEvent{},
+ },
+ }
+
+ for _, c := range cases {
+ actual := getPodsEventWarnings(c.events)
+ if !reflect.DeepEqual(actual, c.expected) {
+ t.Errorf("getPodsEventErrors(%#v) == \n%#v\nexpected \n%#v\n",
+ c.events, actual, c.expected)
+ }
+ }
+}
+
+func TestFilterEventsByType(t *testing.T) {
+ events := []api.Event{
+ {Type: api.EventTypeNormal},
+ {Type: api.EventTypeWarning},
+ }
+
+ cases := []struct {
+ events []api.Event
+ eventType string
+ expected []api.Event
+ }{
+ {nil, "", nil},
+ {nil, api.EventTypeWarning, nil},
+ {
+ events,
+ "",
+ events,
+ },
+ {
+ events,
+ api.EventTypeNormal,
+ []api.Event{
+ {Type: api.EventTypeNormal},
+ },
+ },
+ {
+ events,
+ api.EventTypeWarning,
+ []api.Event{
+ {Type: api.EventTypeWarning},
+ },
+ },
+ }
+
+ for _, c := range cases {
+ actual := filterEventsByType(c.events, c.eventType)
+ if !reflect.DeepEqual(actual, c.expected) {
+ t.Errorf("FilterEventsByType(%#v, %#v) == \n%#v\nexpected \n%#v\n",
+ c.events, c.eventType, actual, c.expected)
+ }
+ }
+}
+
+func TestRemoveDuplicates(t *testing.T) {
+ cases := []struct {
+ slice []PodEvent
+ expected []PodEvent
+ }{
+ {nil, []PodEvent{}},
+ {
+ []PodEvent{
+ {Reason: "test"},
+ {Reason: "test2"},
+ {Reason: "test"},
+ },
+ []PodEvent{
+ {Reason: "test"},
+ {Reason: "test2"},
+ },
+ },
+ {
+ []PodEvent{
+ {Reason: "test"},
+ {Reason: "test"},
+ {Reason: "test"},
+ },
+ []PodEvent{
+ {Reason: "test"},
+ },
+ },
+ {
+ []PodEvent{
+ {Reason: "test"},
+ {Reason: "test2"},
+ {Reason: "test3"},
+ },
+ []PodEvent{
+ {Reason: "test"},
+ {Reason: "test2"},
+ {Reason: "test3"},
+ },
+ },
+ }
+
+ for _, c := range cases {
+ actual := removeDuplicates(c.slice)
+ if !reflect.DeepEqual(actual, c.expected) {
+ t.Errorf("removeDuplicates(%#v) == \n%#v\nexpected \n%#v\n",
+ c.slice, actual, c.expected)
+ }
+ }
+}
+
+func TestIsRunningOrSucceeded(t *testing.T) {
+ cases := []struct {
+ pod api.Pod
+ expected bool
+ }{
+ {
+ api.Pod{
+ Status: api.PodStatus{
+ Phase: api.PodRunning,
+ },
+ },
+ true,
+ },
+ {
+ api.Pod{
+ Status: api.PodStatus{
+ Phase: api.PodSucceeded,
+ },
+ },
+ true,
+ },
+ {
+ api.Pod{
+ Status: api.PodStatus{
+ Phase: api.PodFailed,
+ },
+ },
+ false,
+ },
+ {
+ api.Pod{
+ Status: api.PodStatus{
+ Phase: api.PodPending,
+ },
+ },
+ false,
+ },
+ }
+
+ for _, c := range cases {
+ actual := isRunningOrSucceeded(c.pod)
+ if !reflect.DeepEqual(actual, c.expected) {
+ t.Errorf("isRunningOrSucceded(%#v) == \n%#v\nexpected \n%#v\n",
+ c.pod, actual, c.expected)
+ }
+ }
+}
+
+func TestFilterEventsByReason(t *testing.T) {
+ cases := []struct {
+ events []api.Event
+ partial string
+ expected []api.Event
+ }{
+ {nil, "", nil},
+ {nil, "failed", nil},
+ {
+ []api.Event{
+ {
+ Message: "msg",
+ Reason: "reason",
+ },
+ {
+ Message: "msg-2",
+ Reason: "failed",
+ },
+ },
+ "failed",
+ []api.Event{
+ {
+ Message: "msg-2",
+ Reason: "failed",
+ },
+ },
+ },
+ }
+
+ for _, c := range cases {
+ actual := filterEventsByReason(c.events, c.partial)
+ if !reflect.DeepEqual(actual, c.expected) {
+ t.Errorf("filterEventsByReason(%#v, %#v) == \n%#v\nexpected \n%#v\n",
+ c.events, c.partial, actual, c.expected)
+ }
+ }
+}
+
+func TestIsTypeFilled(t *testing.T) {
+ cases := []struct {
+ events []api.Event
+ expected bool
+ }{
+ {nil, false},
+ {
+ []api.Event{
+ {Type: api.EventTypeWarning},
+ },
+ true,
+ },
+ {
+ []api.Event{},
+ false,
+ },
+ {
+ []api.Event{
+ {Type: api.EventTypeWarning},
+ {Type: api.EventTypeNormal},
+ {Type: ""},
+ },
+ false,
+ },
+ }
+
+ for _, c := range cases {
+ actual := isTypeFilled(c.events)
+ if !reflect.DeepEqual(actual, c.expected) {
+ t.Errorf("isTypeFilled(%#v) == \n%#v\nexpected \n%#v\n",
+ c.events, actual, c.expected)
+ }
+ }
+}
diff --git a/src/test/backend/replicasetcommon_test.go b/src/test/backend/replicasetcommon_test.go
new file mode 100644
index 000000000000..6a9f519b1558
--- /dev/null
+++ b/src/test/backend/replicasetcommon_test.go
@@ -0,0 +1,63 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// 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 main
+
+import (
+ "reflect"
+ "testing"
+
+ "k8s.io/kubernetes/pkg/api"
+)
+
+func TestGetReplicaSetPodInfo(t *testing.T) {
+ cases := []struct {
+ controller *api.ReplicationController
+ pods []api.Pod
+ expected ReplicationControllerPodInfo
+ }{
+ {
+ &api.ReplicationController{
+ Status: api.ReplicationControllerStatus{
+ Replicas: 5,
+ },
+ Spec: api.ReplicationControllerSpec{
+ Replicas: 4,
+ },
+ },
+ []api.Pod{
+ {
+ Status: api.PodStatus{
+ Phase: api.PodRunning,
+ },
+ },
+ },
+ ReplicationControllerPodInfo{
+ Current: 5,
+ Desired: 4,
+ Running: 1,
+ Pending: 0,
+ Failed: 0,
+ },
+ },
+ }
+
+ for _, c := range cases {
+ actual := getReplicationControllerPodInfo(c.controller, c.pods)
+ if !reflect.DeepEqual(actual, c.expected) {
+ t.Errorf("getReplicaSetPodInfo(%#v, %#v) == \n%#v\nexpected \n%#v\n",
+ c.controller, c.pods, actual, c.expected)
+ }
+ }
+}
diff --git a/src/test/backend/replicationcontrollerdetail_test.go b/src/test/backend/replicationcontrollerdetail_test.go
index f3b2d476b5c6..26f1ecc2349f 100644
--- a/src/test/backend/replicationcontrollerdetail_test.go
+++ b/src/test/backend/replicationcontrollerdetail_test.go
@@ -26,8 +26,8 @@ func TestUpdateReplicasCount(t *testing.T) {
cases := []struct {
namespace, replicationControllerName string
replicationControllerSpec *ReplicationControllerSpec
- expected int
- expectedActions []string
+ expected int
+ expectedActions []string
}{
{
"default-ns", "replicationController-1",
diff --git a/src/test/backend/replicationcontrollerlist_test.go b/src/test/backend/replicationcontrollerlist_test.go
index 57873aa370a3..8a1ea07df1c2 100644
--- a/src/test/backend/replicationcontrollerlist_test.go
+++ b/src/test/backend/replicationcontrollerlist_test.go
@@ -24,7 +24,7 @@ import (
func TestIsLabelSelectorMatching(t *testing.T) {
cases := []struct {
serviceSelector, replicationControllerSelector map[string]string
- expected bool
+ expected bool
}{
{nil, nil, false},
{nil, map[string]string{}, false},
@@ -52,9 +52,9 @@ func TestIsLabelSelectorMatching(t *testing.T) {
func TestGetMatchingServices(t *testing.T) {
cases := []struct {
- services []api.Service
+ services []api.Service
replicationController *api.ReplicationController
- expected []api.Service
+ expected []api.Service
}{
{nil, nil, nil},
{
@@ -83,11 +83,15 @@ func TestGetMatchingServices(t *testing.T) {
}
func TestGetReplicationControllerList(t *testing.T) {
+ getPodsErrorFnMock := func(pods []api.Pod) ([]PodEvent, error) {
+ return []PodEvent{}, nil
+ }
+
cases := []struct {
replicationControllers []api.ReplicationController
- services []api.Service
- pods []api.Pod
- expected *ReplicationControllerList
+ services []api.Service
+ pods []api.Pod
+ expected *ReplicationControllerList
}{
{nil, nil, nil, &ReplicationControllerList{ReplicationControllers: []ReplicationController{}}},
{
@@ -206,23 +210,27 @@ func TestGetReplicationControllerList(t *testing.T) {
ContainerImages: []string{"my-container-image-1"},
InternalEndpoints: []Endpoint{{Host: "my-app-1.namespace-1"}},
Pods: ReplicationControllerPodInfo{
- Failed: 2,
- Pending: 1,
- Running: 1,
+ Failed: 2,
+ Pending: 1,
+ Running: 1,
+ Warnings: []PodEvent{},
},
}, {
Name: "my-app-2",
Namespace: "namespace-2",
ContainerImages: []string{"my-container-image-2"},
InternalEndpoints: []Endpoint{{Host: "my-app-2.namespace-2"}},
- Pods: ReplicationControllerPodInfo{},
+ Pods: ReplicationControllerPodInfo{
+ Warnings: []PodEvent{},
+ },
},
},
},
},
}
for _, c := range cases {
- actual := getReplicationControllerList(c.replicationControllers, c.services, c.pods)
+ actual, _ := getReplicationControllerList(c.replicationControllers, c.services, c.pods,
+ getPodsErrorFnMock)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("getReplicationControllerList(%#v, %#v) == \n%#v\nexpected \n%#v\n",
c.replicationControllers, c.services, actual, c.expected)
diff --git a/src/test/frontend/replicationcontrollerlist/replicationcontrollercard_controller_test.js b/src/test/frontend/replicationcontrollerlist/replicationcontrollercard_controller_test.js
index 3501fee61e55..6da38f2991f4 100644
--- a/src/test/frontend/replicationcontrollerlist/replicationcontrollercard_controller_test.js
+++ b/src/test/frontend/replicationcontrollerlist/replicationcontrollercard_controller_test.js
@@ -15,7 +15,7 @@
import ReplicationControllerCardController from 'replicationcontrollerlist/replicationcontrollercard_controller';
import replicationControllerListModule from 'replicationcontrollerlist/replicationcontrollerlist_module';
-describe('Logs menu controller', () => {
+describe('Replication controller card controller', () => {
/**
* @type {!ReplicationControllerCardController}
*/
@@ -72,4 +72,73 @@ describe('Logs menu controller', () => {
// then
expect(ctrl.areDesiredPodsRunning()).toBeFalsy();
});
+
+ it('should return true when at least one replication controller pod has warning', () => {
+ // given
+ ctrl.replicationController = {
+ pods: {
+ warnings: [{
+ message: "test-error",
+ reason: "test-reason",
+ }],
+ },
+ };
+
+ // then
+ expect(ctrl.hasWarnings()).toBeTruthy();
+ });
+
+ it('should return false when there are no errors related to replication controller pods', () => {
+ // given
+ ctrl.replicationController = {
+ pods: {
+ warnings: [],
+ },
+ };
+
+ // then
+ expect(ctrl.hasWarnings()).toBeFalsy();
+ });
+
+ it('should return true when there are no warnings and at least one pod is in pending state',
+ () => {
+ // given
+ ctrl.replicationController = {
+ pods: {
+ warnings: [],
+ pending: 1,
+ },
+ };
+
+ // then
+ expect(ctrl.isPending()).toBeTruthy();
+ });
+
+ it('should return false when there is warning related to replication controller pods', () => {
+ // given
+ ctrl.replicationController = {
+ pods: {
+ warnings: [{
+ message: "test-error",
+ reason: "test-reason",
+ }],
+ },
+ };
+
+ // then
+ expect(ctrl.isPending()).toBeFalsy();
+ });
+
+ it('should return false when there are no warnings and there is no pod in pending state', () => {
+ // given
+ ctrl.replicationController = {
+ pods: {
+ warnings: [],
+ pending: 0,
+ },
+ };
+
+ // then
+ expect(ctrl.isPending()).toBeFalsy();
+ });
});