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 @@
- -

+ + + {{::ctrl.replicationController.name}} -

+ + + error + One or more pods have errors + + + timelapse + One or more pods are in pending state + +
@@ -33,7 +45,7 @@

- @@ -107,6 +119,15 @@

+
+ +
+
+ {{::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(); + }); });