From 95ada743f84cf17fe21daaaf8fad5d5fe6b7f824 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Wed, 1 Jun 2016 13:47:51 +0200 Subject: [PATCH] Add Job resource backend --- i18n/messages-en.xtb | 2 + i18n/messages-ja.xtb | 2 + src/app/backend/handler/apihandler.go | 42 +++- .../resource/common/resourcechannels.go | 37 ++++ src/app/backend/resource/common/types.go | 7 +- src/app/backend/resource/common/verber.go | 8 +- src/app/backend/resource/job/jobcommon.go | 26 +++ src/app/backend/resource/job/jobdetail.go | 94 +++++++++ src/app/backend/resource/job/jobevents.go | 82 ++++++++ src/app/backend/resource/job/joblist.go | 130 ++++++++++++ .../backend/resource/workload/workloads.go | 28 ++- src/app/frontend/chrome/chrome_controller.js | 3 +- .../daemonsetdetail_stateconfig.js | 2 +- .../deploymentdetail_stateconfig.js | 2 +- .../error/internalerror_stateconfig.js | 4 +- .../poddetail/poddetail_stateconfig.js | 2 +- .../replicasetdetail_stateconfig.js | 2 +- .../workloads/workloads_stateconfig.js | 2 +- .../backend/resource/job/jobcommon_test.go | 67 +++++++ .../backend/resource/job/jobdetail_test.go | 99 ++++++++++ .../backend/resource/job/jobevents_test.go | 138 +++++++++++++ src/test/backend/resource/job/joblist_test.go | 187 ++++++++++++++++++ .../resource/workload/workloads_test.go | 71 +++++-- 23 files changed, 1005 insertions(+), 32 deletions(-) create mode 100644 src/app/backend/resource/job/jobcommon.go create mode 100644 src/app/backend/resource/job/jobdetail.go create mode 100644 src/app/backend/resource/job/jobevents.go create mode 100644 src/app/backend/resource/job/joblist.go create mode 100644 src/test/backend/resource/job/jobcommon_test.go create mode 100644 src/test/backend/resource/job/jobdetail_test.go create mode 100644 src/test/backend/resource/job/jobevents_test.go create mode 100644 src/test/backend/resource/job/joblist_test.go diff --git a/i18n/messages-en.xtb b/i18n/messages-en.xtb index c0781439b601..54c469824540 100644 --- a/i18n/messages-en.xtb +++ b/i18n/messages-en.xtb @@ -142,4 +142,6 @@ Images namespace not selected Selector for namespaces + One or more pods have errors. + One or more pods are in pending state. \ No newline at end of file diff --git a/i18n/messages-ja.xtb b/i18n/messages-ja.xtb index 8fb106429642..3682d5ca56ad 100644 --- a/i18n/messages-ja.xtb +++ b/i18n/messages-ja.xtb @@ -142,4 +142,6 @@ Images namespace not selected Selector for namespaces + One or more pods have errors. + One or more pods are in pending state. \ No newline at end of file diff --git a/src/app/backend/handler/apihandler.go b/src/app/backend/handler/apihandler.go index f758488cf8eb..55b62785ed18 100644 --- a/src/app/backend/handler/apihandler.go +++ b/src/app/backend/handler/apihandler.go @@ -28,6 +28,7 @@ import ( . "github.com/kubernetes/dashboard/resource/container" "github.com/kubernetes/dashboard/resource/daemonset" "github.com/kubernetes/dashboard/resource/deployment" + "github.com/kubernetes/dashboard/resource/job" . "github.com/kubernetes/dashboard/resource/namespace" "github.com/kubernetes/dashboard/resource/petset" "github.com/kubernetes/dashboard/resource/pod" @@ -88,7 +89,7 @@ func CreateHttpApiHandler(client *client.Client, heapsterClient HeapsterClient, clientConfig clientcmd.ClientConfig) http.Handler { verber := common.NewResourceVerber(client.RESTClient, client.ExtensionsClient.RESTClient, - client.AppsClient.RESTClient) + client.AppsClient.RESTClient, client.BatchClient.RESTClient) apiHandler := ApiHandler{client, heapsterClient, clientConfig, verber} wsContainer := restful.NewContainer() @@ -218,6 +219,19 @@ func CreateHttpApiHandler(client *client.Client, heapsterClient HeapsterClient, apiV1Ws.DELETE("/daemonset/{namespace}/{daemonSet}"). To(apiHandler.handleDeleteDaemonSet)) + apiV1Ws.Route( + apiV1Ws.GET("/job"). + To(apiHandler.handleGetJobs). + Writes(job.JobList{})) + apiV1Ws.Route( + apiV1Ws.GET("/job/{namespace}"). + To(apiHandler.handleGetJobs). + Writes(job.JobList{})) + apiV1Ws.Route( + apiV1Ws.GET("/job/{namespace}/{job}"). + To(apiHandler.handleGetJobDetail). + Writes(job.JobDetail{})) + apiV1Ws.Route( apiV1Ws.POST("/namespace"). To(apiHandler.handleCreateNamespace). @@ -756,6 +770,32 @@ func (apiHandler *ApiHandler) handleDeleteDaemonSet( response.WriteHeader(http.StatusOK) } +// Handles get Jobs list API call. +func (apiHandler *ApiHandler) handleGetJobs(request *restful.Request, response *restful.Response) { + namespace := parseNamespacePathParameter(request) + + result, err := job.GetJobList(apiHandler.client, namespace) + if err != nil { + handleInternalError(response, err) + return + } + + response.WriteHeaderAndEntity(http.StatusCreated, result) +} + +func (apiHandler *ApiHandler) handleGetJobDetail(request *restful.Request, response *restful.Response) { + namespace := request.PathParameter("namespace") + jobParam := request.PathParameter("job") + + result, err := job.GetJobDetail(apiHandler.client, apiHandler.heapsterClient, namespace, jobParam) + if err != nil { + handleInternalError(response, err) + return + } + + response.WriteHeaderAndEntity(http.StatusCreated, result) +} + // parseNamespacePathParameter parses namespace selector for list pages in path paramater. // The namespace selector is a comma separated list of namespaces that are trimmed. // No namespaces means "view all user namespaces", i.e., everything except kube-system. diff --git a/src/app/backend/resource/common/resourcechannels.go b/src/app/backend/resource/common/resourcechannels.go index 5063927c0762..7e4dcc4d0c9b 100644 --- a/src/app/backend/resource/common/resourcechannels.go +++ b/src/app/backend/resource/common/resourcechannels.go @@ -17,6 +17,7 @@ package common import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/fields" @@ -47,6 +48,9 @@ type ResourceChannels struct { // List and error channels to Daemon Sets. DaemonSetList DaemonSetListChannel + // List and error channels to Jobs. + JobList JobListChannel + // List and error channels to Services. ServiceList ServiceListChannel @@ -343,6 +347,39 @@ func GetDaemonSetListChannel(client client.DaemonSetsNamespacer, return channel } +// JobListChannel is a list and error channels to Nodes. +type JobListChannel struct { + List chan *batch.JobList + Error chan error +} + +// GetJobListChannel returns a pair of channels to a Job list and errors that +// both must be read numReads times. +func GetJobListChannel(client client.JobsNamespacer, + nsQuery *NamespaceQuery, numReads int) JobListChannel { + channel := JobListChannel{ + List: make(chan *batch.JobList, numReads), + Error: make(chan error, numReads), + } + + go func() { + list, err := client.Jobs(nsQuery.ToRequestParam()).List(listEverything) + var filteredItems []batch.Job + for _, item := range list.Items { + if nsQuery.Matches(item.ObjectMeta.Namespace) { + filteredItems = append(filteredItems, item) + } + } + list.Items = filteredItems + for i := 0; i < numReads; i++ { + channel.List <- list + channel.Error <- err + } + }() + + return channel +} + // PetSetListChannel is a list and error channels to Nodes. type PetSetListChannel struct { List chan *apps.PetSetList diff --git a/src/app/backend/resource/common/types.go b/src/app/backend/resource/common/types.go index 9c90bd06ccd8..8105790520fe 100644 --- a/src/app/backend/resource/common/types.go +++ b/src/app/backend/resource/common/types.go @@ -95,6 +95,7 @@ const ( ResourceKindEvent = "event" ResourceKindReplicationController = "replicationcontroller" ResourceKindDaemonSet = "daemonset" + ResourceKindJob = "job" ResourceKindPetSet = "petset" ) @@ -105,9 +106,10 @@ type ClientType string // List of client types supported by the UI. const ( - ClientTypeDefault = "restclient" + ClientTypeDefault = "restclient" ClientTypeExtensionClient = "extensionclient" - ClientTypeAppsClient = "appsclient" + ClientTypeAppsClient = "appsclient" + ClientTypeBatchClient = "batchclient" ) // Mapping from resource kind to K8s apiserver API path. This is mostly pluralization, because @@ -128,6 +130,7 @@ var kindToAPIMapping = map[string]struct { ResourceKindReplicaSet: {"replicasets", ClientTypeExtensionClient}, ResourceKindDaemonSet: {"daemonsets", ClientTypeDefault}, ResourceKindPetSet: {"petsets", ClientTypeAppsClient}, + ResourceKindJob: {"jobs", ClientTypeBatchClient}, } // IsSelectorMatching returns true when an object with the given diff --git a/src/app/backend/resource/common/verber.go b/src/app/backend/resource/common/verber.go index c23831475446..da237e02250f 100644 --- a/src/app/backend/resource/common/verber.go +++ b/src/app/backend/resource/common/verber.go @@ -26,6 +26,7 @@ type ResourceVerber struct { client RESTClient extensionsClient RESTClient appsClient RESTClient + batchClient RESTClient } // RESTClient is an interface for REST operations used in this file. @@ -35,8 +36,9 @@ type RESTClient interface { // NewResourceVerber creates a new resource verber that uses the given client for performing // operations. -func NewResourceVerber(client, extensionsClient, appsClient RESTClient) ResourceVerber { - return ResourceVerber{client, extensionsClient, appsClient} +func NewResourceVerber(client, extensionsClient, appsClient, + batchClient RESTClient) ResourceVerber { + return ResourceVerber{client, extensionsClient, appsClient, batchClient} } // Delete deletes the resource of the given kind in the given namespace with the given name. @@ -62,6 +64,8 @@ func (verber *ResourceVerber) getRESTClientByType(clientType ClientType) RESTCli return verber.extensionsClient case ClientTypeAppsClient: return verber.appsClient + case ClientTypeBatchClient: + return verber.batchClient default: return verber.client } diff --git a/src/app/backend/resource/job/jobcommon.go b/src/app/backend/resource/job/jobcommon.go new file mode 100644 index 000000000000..ca2828c1f39f --- /dev/null +++ b/src/app/backend/resource/job/jobcommon.go @@ -0,0 +1,26 @@ +// 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 job + +import ( + "github.com/kubernetes/dashboard/resource/common" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/batch" +) + +// getPodInfo returns aggregate information about job pods. +func getPodInfo(resource *batch.Job, pods []api.Pod) common.PodInfo { + return common.GetPodInfo(resource.Status.Active, *resource.Spec.Completions, pods) +} diff --git a/src/app/backend/resource/job/jobdetail.go b/src/app/backend/resource/job/jobdetail.go new file mode 100644 index 000000000000..08faa0a45c45 --- /dev/null +++ b/src/app/backend/resource/job/jobdetail.go @@ -0,0 +1,94 @@ +// 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 job + +import ( + "log" + + "github.com/kubernetes/dashboard/client" + "github.com/kubernetes/dashboard/resource/common" + "github.com/kubernetes/dashboard/resource/pod" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/batch" + k8sClient "k8s.io/kubernetes/pkg/client/unversioned" +) + +// JobDetail is a presentation layer view of Kubernetes Job resource. This means +// it is Job plus additional augmented data we can get from other sources +// (like services that target the same pods). +type JobDetail struct { + ObjectMeta common.ObjectMeta `json:"objectMeta"` + TypeMeta common.TypeMeta `json:"typeMeta"` + + // Aggregate information about pods belonging to this Job. + PodInfo common.PodInfo `json:"podInfo"` + + // Detailed information about Pods belonging to this Job. + PodList pod.PodList `json:"podList"` + + // Container images of the Job. + ContainerImages []string `json:"containerImages"` + + // List of events related to this Job. + EventList common.EventList `json:"eventList"` +} + +// GetJobDetail gets job details. +func GetJobDetail(client k8sClient.Interface, heapsterClient client.HeapsterClient, + namespace, name string) (*JobDetail, error) { + + log.Printf("Getting details of %s service in %s namespace", name, namespace) + + // TODO(floreks): Use channels. + jobData, err := client.Extensions().Jobs(namespace).Get(name) + if err != nil { + return nil, err + } + + channels := &common.ResourceChannels{ + PodList: common.GetPodListChannel(client, common.NewSameNamespaceQuery(namespace), 1), + } + + pods := <-channels.PodList.List + if err := <-channels.PodList.Error; err != nil { + return nil, err + } + + events, err := GetJobEvents(client, jobData.Namespace, jobData.Name) + if err != nil { + return nil, err + } + + job := getJobDetail(jobData, heapsterClient, events, pods.Items) + return &job, nil +} + +func getJobDetail(job *batch.Job, heapsterClient client.HeapsterClient, + events *common.EventList, pods []api.Pod) JobDetail { + + matchingPods := common.FilterNamespacedPodsBySelector(pods, job.ObjectMeta.Namespace, + job.Spec.Selector.MatchLabels) + + podInfo := getPodInfo(job, matchingPods) + + return JobDetail{ + ObjectMeta: common.NewObjectMeta(job.ObjectMeta), + TypeMeta: common.NewTypeMeta(common.ResourceKindJob), + ContainerImages: common.GetContainerImages(&job.Spec.Template.Spec), + PodInfo: podInfo, + PodList: pod.CreatePodList(matchingPods, heapsterClient), + EventList: *events, + } +} diff --git a/src/app/backend/resource/job/jobevents.go b/src/app/backend/resource/job/jobevents.go new file mode 100644 index 000000000000..c72c7cae3e68 --- /dev/null +++ b/src/app/backend/resource/job/jobevents.go @@ -0,0 +1,82 @@ +// 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 job + +import ( + "log" + + "github.com/kubernetes/dashboard/resource/common" + "github.com/kubernetes/dashboard/resource/event" + + "k8s.io/kubernetes/pkg/api" + client "k8s.io/kubernetes/pkg/client/unversioned" +) + +// GetJobEvents gets events associated to job. +func GetJobEvents(client client.Interface, namespace, jobName string) ( + *common.EventList, error) { + + log.Printf("Getting events related to %s job in %s namespace", jobName, + namespace) + + // Get events for job. + rsEvents, err := event.GetEvents(client, namespace, jobName) + + if err != nil { + return nil, err + } + + // Get events for pods in job. + podEvents, err := GetJobPodsEvents(client, namespace, jobName) + + if err != nil { + return nil, err + } + + apiEvents := append(rsEvents, podEvents...) + + if !event.IsTypeFilled(apiEvents) { + apiEvents = event.FillEventsType(apiEvents) + } + + events := event.AppendEvents(apiEvents, common.EventList{ + Namespace: namespace, + Events: make([]common.Event, 0), + }) + + log.Printf("Found %d events related to %s job in %s namespace", + len(events.Events), jobName, namespace) + + return &events, nil +} + +// GetJobPodsEvents gets events associated to pods in job. +func GetJobPodsEvents(client client.Interface, namespace, jobName string) ( + []api.Event, error) { + + job, err := client.Extensions().Jobs(namespace).Get(jobName) + + if err != nil { + return nil, err + } + + podEvents, err := event.GetPodsEvents(client, namespace, job.Spec.Selector.MatchLabels) + + if err != nil { + return nil, err + } + + return podEvents, nil +} diff --git a/src/app/backend/resource/job/joblist.go b/src/app/backend/resource/job/joblist.go new file mode 100644 index 000000000000..7969a5657a3d --- /dev/null +++ b/src/app/backend/resource/job/joblist.go @@ -0,0 +1,130 @@ +// 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 job + +import ( + "log" + + "github.com/kubernetes/dashboard/resource/common" + "k8s.io/kubernetes/pkg/api" + k8serrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/apis/batch" + client "k8s.io/kubernetes/pkg/client/unversioned" +) + +// JobList contains a list of Jobs in the cluster. +type JobList struct { + // Unordered list of Jobs. + Jobs []Job `json:"jobs"` +} + +// Job is a presentation layer view of Kubernetes Job resource. This means +// it is Job plus additional augumented data we can get from other sources +type Job struct { + ObjectMeta common.ObjectMeta `json:"objectMeta"` + TypeMeta common.TypeMeta `json:"typeMeta"` + + // Aggregate information about pods belonging to this Job. + Pods common.PodInfo `json:"pods"` + + // Container images of the Job. + ContainerImages []string `json:"containerImages"` +} + +// GetJobList returns a list of all Jobs in the cluster. +func GetJobList(client client.Interface, nsQuery *common.NamespaceQuery) (*JobList, error) { + log.Printf("Getting list of all jobs in the cluster") + + channels := &common.ResourceChannels{ + JobList: common.GetJobListChannel(client.Extensions(), nsQuery, 1), + ServiceList: common.GetServiceListChannel(client, nsQuery, 1), + PodList: common.GetPodListChannel(client, nsQuery, 1), + EventList: common.GetEventListChannel(client, nsQuery, 1), + NodeList: common.GetNodeListChannel(client, nsQuery, 1), + } + + return GetJobListFromChannels(channels) +} + +// GetJobList returns a list of all Jobs in the cluster +// reading required resource list once from the channels. +func GetJobListFromChannels(channels *common.ResourceChannels) ( + *JobList, error) { + + jobs := <-channels.JobList.List + if err := <-channels.JobList.Error; err != nil { + statusErr, ok := err.(*k8serrors.StatusError) + if ok && statusErr.ErrStatus.Reason == "NotFound" { + // NotFound - this means that the server does not support Job objects, which + // is fine. + emptyList := &JobList{ + Jobs: make([]Job, 0), + } + return emptyList, nil + } + return nil, err + } + + services := <-channels.ServiceList.List + if err := <-channels.ServiceList.Error; err != nil { + return nil, err + } + + pods := <-channels.PodList.List + if err := <-channels.PodList.Error; err != nil { + return nil, err + } + + events := <-channels.EventList.List + if err := <-channels.EventList.Error; err != nil { + return nil, err + } + + nodes := <-channels.NodeList.List + if err := <-channels.NodeList.Error; err != nil { + return nil, err + } + + return ToJobList(jobs.Items, services.Items, pods.Items, events.Items, + nodes.Items), nil +} + +func ToJobList(jobs []batch.Job, + services []api.Service, pods []api.Pod, events []api.Event, + nodes []api.Node) *JobList { + + jobList := &JobList{ + Jobs: make([]Job, 0), + } + + for _, job := range jobs { + matchingPods := common.FilterNamespacedPodsBySelector(pods, job.ObjectMeta.Namespace, + job.Spec.Selector.MatchLabels) + podInfo := getPodInfo(&job, matchingPods) + + jobList.Jobs = append(jobList.Jobs, ToJob(&job, &podInfo)) + } + + return jobList +} + +func ToJob(job *batch.Job, podInfo *common.PodInfo) Job { + return Job{ + ObjectMeta: common.NewObjectMeta(job.ObjectMeta), + TypeMeta: common.NewTypeMeta(common.ResourceKindJob), + ContainerImages: common.GetContainerImages(&job.Spec.Template.Spec), + Pods: *podInfo, + } +} diff --git a/src/app/backend/resource/workload/workloads.go b/src/app/backend/resource/workload/workloads.go index cbafa5406cf9..66c9ac372169 100644 --- a/src/app/backend/resource/workload/workloads.go +++ b/src/app/backend/resource/workload/workloads.go @@ -21,6 +21,7 @@ import ( "github.com/kubernetes/dashboard/resource/common" "github.com/kubernetes/dashboard/resource/daemonset" "github.com/kubernetes/dashboard/resource/deployment" + "github.com/kubernetes/dashboard/resource/job" "github.com/kubernetes/dashboard/resource/petset" "github.com/kubernetes/dashboard/resource/pod" "github.com/kubernetes/dashboard/resource/replicaset" @@ -34,6 +35,8 @@ type Workloads struct { ReplicaSetList replicaset.ReplicaSetList `json:"replicaSetList"` + JobList job.JobList `json:"jobList"` + ReplicationControllerList replicationcontroller.ReplicationControllerList `json:"replicationControllerList"` PodList pod.PodList `json:"podList"` @@ -51,13 +54,14 @@ func GetWorkloads(client *k8sClient.Client, heapsterClient client.HeapsterClient channels := &common.ResourceChannels{ ReplicationControllerList: common.GetReplicationControllerListChannel(client, nsQuery, 1), ReplicaSetList: common.GetReplicaSetListChannel(client.Extensions(), nsQuery, 1), + JobList: common.GetJobListChannel(client.Batch(), nsQuery, 1), DaemonSetList: common.GetDaemonSetListChannel(client.Extensions(), nsQuery, 1), DeploymentList: common.GetDeploymentListChannel(client.Extensions(), nsQuery, 1), PetSetList: common.GetPetSetListChannel(client.Apps(), nsQuery, 1), - ServiceList: common.GetServiceListChannel(client, nsQuery, 5), - PodList: common.GetPodListChannel(client, nsQuery, 6), - EventList: common.GetEventListChannel(client, nsQuery, 5), - NodeList: common.GetNodeListChannel(client, nsQuery, 5), + ServiceList: common.GetServiceListChannel(client, nsQuery, 6), + PodList: common.GetPodListChannel(client, nsQuery, 7), + EventList: common.GetEventListChannel(client, nsQuery, 6), + NodeList: common.GetNodeListChannel(client, nsQuery, 6), } return GetWorkloadsFromChannels(channels, heapsterClient) @@ -69,12 +73,13 @@ func GetWorkloadsFromChannels(channels *common.ResourceChannels, heapsterClient client.HeapsterClient) (*Workloads, error) { rsChan := make(chan *replicaset.ReplicaSetList) + jobChan := make(chan *job.JobList) deploymentChan := make(chan *deployment.DeploymentList) rcChan := make(chan *replicationcontroller.ReplicationControllerList) podChan := make(chan *pod.PodList) dsChan := make(chan *daemonset.DaemonSetList) psChan := make(chan *petset.PetSetList) - errChan := make(chan error, 6) + errChan := make(chan error, 7) go func() { rcList, err := replicationcontroller.GetReplicationControllerListFromChannels(channels) @@ -88,6 +93,12 @@ func GetWorkloadsFromChannels(channels *common.ResourceChannels, rsChan <- rsList }() + go func() { + jobList, err := job.GetJobListFromChannels(channels) + errChan <- err + jobChan <- jobList + }() + go func() { deploymentList, err := deployment.GetDeploymentListFromChannels(channels) errChan <- err @@ -130,6 +141,12 @@ func GetWorkloadsFromChannels(channels *common.ResourceChannels, return nil, err } + jobList := <-jobChan + err = <-errChan + if err != nil { + return nil, err + } + deploymentList := <-deploymentChan err = <-errChan if err != nil { @@ -150,6 +167,7 @@ func GetWorkloadsFromChannels(channels *common.ResourceChannels, workloads := &Workloads{ ReplicaSetList: *rsList, + JobList: *jobList, ReplicationControllerList: *rcList, DeploymentList: *deploymentList, PodList: *podList, diff --git a/src/app/frontend/chrome/chrome_controller.js b/src/app/frontend/chrome/chrome_controller.js index 8a875159d6d4..ca8d6575cad1 100644 --- a/src/app/frontend/chrome/chrome_controller.js +++ b/src/app/frontend/chrome/chrome_controller.js @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {actionbarViewName} from './chrome_state'; import {stateName as workloadState} from 'workloads/workloads_state'; +import {actionbarViewName} from './chrome_state'; + /** * Controller for the chrome directive. * diff --git a/src/app/frontend/daemonsetdetail/daemonsetdetail_stateconfig.js b/src/app/frontend/daemonsetdetail/daemonsetdetail_stateconfig.js index 3b45230a7e5b..aa26f1989a94 100644 --- a/src/app/frontend/daemonsetdetail/daemonsetdetail_stateconfig.js +++ b/src/app/frontend/daemonsetdetail/daemonsetdetail_stateconfig.js @@ -14,8 +14,8 @@ import {actionbarViewName, stateName as chromeStateName} from 'chrome/chrome_state'; import {breadcrumbsConfig} from 'common/components/breadcrumbs/breadcrumbs_service'; -import {stateName as daemonSetList, stateUrl} from 'daemonsetlist/daemonsetlist_state'; import {appendDetailParamsToUrl} from 'common/resource/resourcedetail'; +import {stateName as daemonSetList, stateUrl} from 'daemonsetlist/daemonsetlist_state'; import {DaemonSetDetailController} from './daemonsetdetail_controller'; import {stateName} from './daemonsetdetail_state'; diff --git a/src/app/frontend/deploymentdetail/deploymentdetail_stateconfig.js b/src/app/frontend/deploymentdetail/deploymentdetail_stateconfig.js index 62a3928bd277..dbf7b7699845 100644 --- a/src/app/frontend/deploymentdetail/deploymentdetail_stateconfig.js +++ b/src/app/frontend/deploymentdetail/deploymentdetail_stateconfig.js @@ -14,8 +14,8 @@ import {actionbarViewName, stateName as chromeStateName} from 'chrome/chrome_state'; import {breadcrumbsConfig} from 'common/components/breadcrumbs/breadcrumbs_service'; -import {stateName as deploymentList} from 'deploymentlist/deploymentlist_state'; import {appendDetailParamsToUrl} from 'common/resource/resourcedetail'; +import {stateName as deploymentList} from 'deploymentlist/deploymentlist_state'; import {DeploymentDetailController} from './deploymentdetail_controller'; import {stateName, stateUrl} from './deploymentdetail_state'; diff --git a/src/app/frontend/error/internalerror_stateconfig.js b/src/app/frontend/error/internalerror_stateconfig.js index aa375d6c0bff..4e3b5f7323b2 100644 --- a/src/app/frontend/error/internalerror_stateconfig.js +++ b/src/app/frontend/error/internalerror_stateconfig.js @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +import {stateName as chromeStateName} from 'chrome/chrome_state'; + import {InternalErrorController} from './internalerror_controller'; import {stateName, StateParams} from './internalerror_state'; -import {stateName as chromeStateName} from 'chrome/chrome_state'; - /** * Configures states for the internal error view. * diff --git a/src/app/frontend/poddetail/poddetail_stateconfig.js b/src/app/frontend/poddetail/poddetail_stateconfig.js index b6fb7776be50..a48fe376f910 100644 --- a/src/app/frontend/poddetail/poddetail_stateconfig.js +++ b/src/app/frontend/poddetail/poddetail_stateconfig.js @@ -14,11 +14,11 @@ import {actionbarViewName, stateName as chromeStateName} from 'chrome/chrome_state'; import {breadcrumbsConfig} from 'common/components/breadcrumbs/breadcrumbs_service'; +import {appendDetailParamsToUrl} from 'common/resource/resourcedetail'; import {stateName as podList, stateUrl} from 'podlist/podlist_state'; import {PodDetailController} from './poddetail_controller'; import {stateName} from './poddetail_state'; -import {appendDetailParamsToUrl} from 'common/resource/resourcedetail'; /** * Configures states for the pod details view. diff --git a/src/app/frontend/replicasetdetail/replicasetdetail_stateconfig.js b/src/app/frontend/replicasetdetail/replicasetdetail_stateconfig.js index a35cf642cad1..a5d652e6fdc8 100644 --- a/src/app/frontend/replicasetdetail/replicasetdetail_stateconfig.js +++ b/src/app/frontend/replicasetdetail/replicasetdetail_stateconfig.js @@ -14,11 +14,11 @@ import {actionbarViewName, stateName as chromeStateName} from 'chrome/chrome_state'; import {breadcrumbsConfig} from 'common/components/breadcrumbs/breadcrumbs_service'; +import {appendDetailParamsToUrl} from 'common/resource/resourcedetail'; import {stateName as replicaSetList, stateUrl} from 'replicasetlist/replicasetlist_state'; import {ReplicaSetDetailController} from './replicasetdetail_controller'; import {stateName} from './replicasetdetail_state'; -import {appendDetailParamsToUrl} from 'common/resource/resourcedetail'; /** * Configures states for the replica set details view. diff --git a/src/app/frontend/workloads/workloads_stateconfig.js b/src/app/frontend/workloads/workloads_stateconfig.js index c4f948736712..082ddfaade0f 100644 --- a/src/app/frontend/workloads/workloads_stateconfig.js +++ b/src/app/frontend/workloads/workloads_stateconfig.js @@ -13,12 +13,12 @@ // limitations under the License. import {actionbarViewName, stateName as chromeStateName} from 'chrome/chrome_state'; +import {breadcrumbsConfig} from 'common/components/breadcrumbs/breadcrumbs_service'; import {WorkloadsController} from './workloads_controller'; import {stateName} from './workloads_state'; import {stateUrl} from './workloads_state'; import {WorkloadsActionBarController} from './workloadsactionbar_controller'; -import {breadcrumbsConfig} from 'common/components/breadcrumbs/breadcrumbs_service'; /** * @param {!ui.router.$stateProvider} $stateProvider diff --git a/src/test/backend/resource/job/jobcommon_test.go b/src/test/backend/resource/job/jobcommon_test.go new file mode 100644 index 000000000000..186fa67707c7 --- /dev/null +++ b/src/test/backend/resource/job/jobcommon_test.go @@ -0,0 +1,67 @@ +// 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 job + +import ( + "reflect" + "testing" + + "github.com/kubernetes/dashboard/resource/common" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/batch" +) + +func TestGetJobPodInfo(t *testing.T) { + var jobCompletions int32 = 4 + cases := []struct { + controller *batch.Job + pods []api.Pod + expected common.PodInfo + }{ + { + &batch.Job{ + Status: batch.JobStatus{ + Active: 5, + }, + Spec: batch.JobSpec{ + Completions: &jobCompletions, + }, + }, + []api.Pod{ + { + Status: api.PodStatus{ + Phase: api.PodRunning, + }, + }, + }, + common.PodInfo{ + Current: 5, + Desired: 4, + Running: 1, + Pending: 0, + Failed: 0, + Warnings: []common.Event{}, + }, + }, + } + + for _, c := range cases { + actual := getPodInfo(c.controller, c.pods) + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("getJobPodInfo(%#v, %#v) == \n%#v\nexpected \n%#v\n", + c.controller, c.pods, actual, c.expected) + } + } +} diff --git a/src/test/backend/resource/job/jobdetail_test.go b/src/test/backend/resource/job/jobdetail_test.go new file mode 100644 index 000000000000..7eb79048befe --- /dev/null +++ b/src/test/backend/resource/job/jobdetail_test.go @@ -0,0 +1,99 @@ +// 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 job + +import ( + "reflect" + "testing" + + "github.com/kubernetes/dashboard/client" + "github.com/kubernetes/dashboard/resource/common" + "github.com/kubernetes/dashboard/resource/pod" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/client/restclient" + k8sClient "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" +) + +type FakeHeapsterClient struct { + client k8sClient.Interface +} + +func (c FakeHeapsterClient) Get(path string) client.RequestInterface { + return &restclient.Request{} +} + +func TestGetJobDetail(t *testing.T) { + eventList := &api.EventList{} + podList := &api.PodList{} + var jobCompletions int32 = 0 + + cases := []struct { + namespace, name string + expectedActions []string + job *batch.Job + expected *JobDetail + }{ + { + "test-namespace", "test-name", + []string{"get", "list", "list", "get", "list", "list"}, + &batch.Job{ + ObjectMeta: api.ObjectMeta{Name: "test-job"}, + Spec: batch.JobSpec{ + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{}, + }, + Completions: &jobCompletions, + }, + }, + &JobDetail{ + ObjectMeta: common.ObjectMeta{Name: "test-job"}, + TypeMeta: common.TypeMeta{Kind: common.ResourceKindJob}, + PodInfo: common.PodInfo{Warnings: []common.Event{}}, + PodList: pod.PodList{Pods: []pod.Pod{}}, + EventList: common.EventList{Events: []common.Event{}}, + }, + }, + } + + for _, c := range cases { + fakeClient := testclient.NewSimpleFake(c.job, podList, eventList, c.job, + podList, eventList) + fakeHeapsterClient := FakeHeapsterClient{client: testclient.NewSimpleFake()} + + actual, _ := GetJobDetail(fakeClient, fakeHeapsterClient, 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("GetEvents(client,heapsterClient,%#v, %#v) == \ngot: %#v, \nexpected %#v", + c.namespace, c.name, actual, c.expected) + } + } +} diff --git a/src/test/backend/resource/job/jobevents_test.go b/src/test/backend/resource/job/jobevents_test.go new file mode 100644 index 000000000000..badc072e2bde --- /dev/null +++ b/src/test/backend/resource/job/jobevents_test.go @@ -0,0 +1,138 @@ +// 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 job + +import ( + "reflect" + "testing" + + "github.com/kubernetes/dashboard/resource/common" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" +) + +func TestGetJobEvents(t *testing.T) { + cases := []struct { + namespace, name string + eventList *api.EventList + podList *api.PodList + job *batch.Job + expectedActions []string + expected *common.EventList + }{ + { + "test-namespace", "test-name", + &api.EventList{Items: []api.Event{ + {Message: "test-message", ObjectMeta: api.ObjectMeta{Namespace: "test-namespace"}}, + }}, + &api.PodList{Items: []api.Pod{{ObjectMeta: api.ObjectMeta{Name: "test-pod"}}}}, + &batch.Job{ + ObjectMeta: api.ObjectMeta{Name: "test-job"}, + Spec: batch.JobSpec{ + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{}, + }}}, + []string{"list", "get", "list", "list"}, + &common.EventList{ + Namespace: "test-namespace", + Events: []common.Event{{ + TypeMeta: common.TypeMeta{Kind: common.ResourceKindEvent}, + ObjectMeta: common.ObjectMeta{Namespace: "test-namespace"}, + Message: "test-message", + Type: api.EventTypeNormal, + }}}, + }, + } + + for _, c := range cases { + fakeClient := testclient.NewSimpleFake(c.eventList, c.job, c.podList, + &api.EventList{}) + + actual, _ := GetJobEvents(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("GetEvents(client,heapsterClient,%#v, %#v) == \ngot: %#v, \nexpected %#v", + c.namespace, c.name, actual, c.expected) + } + } +} + +func TestGetJobPodsEvents(t *testing.T) { + cases := []struct { + namespace, name string + eventList *api.EventList + podList *api.PodList + job *batch.Job + expectedActions []string + expected []api.Event + }{ + { + "test-namespace", "test-name", + &api.EventList{Items: []api.Event{ + {Message: "test-message", ObjectMeta: api.ObjectMeta{Namespace: "test-namespace"}}, + }}, + &api.PodList{Items: []api.Pod{{ObjectMeta: api.ObjectMeta{Name: "test-pod", Namespace: "test-namespace"}}}}, + &batch.Job{ + ObjectMeta: api.ObjectMeta{Name: "test-job", Namespace: "test-namespace"}, + Spec: batch.JobSpec{ + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{}, + }}}, + []string{"get", "list", "list"}, + []api.Event{{Message: "test-message", ObjectMeta: api.ObjectMeta{Namespace: "test-namespace"}}}, + }, + } + + for _, c := range cases { + fakeClient := testclient.NewSimpleFake(c.job, c.podList, c.eventList) + + actual, _ := GetJobPodsEvents(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("GetEvents(client,heapsterClient,%#v, %#v) == \ngot: %#v, \nexpected %#v", + c.namespace, c.name, actual, c.expected) + } + } +} diff --git a/src/test/backend/resource/job/joblist_test.go b/src/test/backend/resource/job/joblist_test.go new file mode 100644 index 000000000000..80f482192019 --- /dev/null +++ b/src/test/backend/resource/job/joblist_test.go @@ -0,0 +1,187 @@ +// 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 job + +import ( + "errors" + "reflect" + "testing" + + "github.com/kubernetes/dashboard/resource/common" + "k8s.io/kubernetes/pkg/api" + k8serrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" +) + +func TestGetJobListFromChannels(t *testing.T) { + var jobCompletions int32 = 21 + cases := []struct { + k8sRs batch.JobList + k8sRsError error + pods *api.PodList + expected *JobList + expectedError error + }{ + { + batch.JobList{}, + nil, + &api.PodList{}, + &JobList{[]Job{}}, + nil, + }, + { + batch.JobList{}, + errors.New("MyCustomError"), + &api.PodList{}, + nil, + errors.New("MyCustomError"), + }, + { + batch.JobList{}, + &k8serrors.StatusError{}, + &api.PodList{}, + nil, + &k8serrors.StatusError{}, + }, + { + batch.JobList{}, + &k8serrors.StatusError{ErrStatus: unversioned.Status{}}, + &api.PodList{}, + nil, + &k8serrors.StatusError{ErrStatus: unversioned.Status{}}, + }, + { + batch.JobList{}, + &k8serrors.StatusError{ErrStatus: unversioned.Status{Reason: "foo-bar"}}, + &api.PodList{}, + nil, + &k8serrors.StatusError{ErrStatus: unversioned.Status{Reason: "foo-bar"}}, + }, + { + batch.JobList{}, + &k8serrors.StatusError{ErrStatus: unversioned.Status{Reason: "NotFound"}}, + &api.PodList{}, + &JobList{ + Jobs: make([]Job, 0), + }, + nil, + }, + { + batch.JobList{ + Items: []batch.Job{{ + ObjectMeta: api.ObjectMeta{ + Name: "rs-name", + Namespace: "rs-namespace", + Labels: map[string]string{"key": "value"}, + CreationTimestamp: unversioned.Unix(111, 222), + }, + Spec: batch.JobSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + Completions: &jobCompletions, + }, + Status: batch.JobStatus{ + Active: 7, + }, + }}, + }, + nil, + &api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + Namespace: "rs-namespace", + Labels: map[string]string{"foo": "bar"}, + }, + Status: api.PodStatus{Phase: api.PodFailed}, + }, + { + ObjectMeta: api.ObjectMeta{ + Namespace: "rs-namespace", + Labels: map[string]string{"foo": "baz"}, + }, + Status: api.PodStatus{Phase: api.PodFailed}, + }, + }, + }, + &JobList{ + []Job{{ + ObjectMeta: common.ObjectMeta{ + Name: "rs-name", + Namespace: "rs-namespace", + Labels: map[string]string{"key": "value"}, + CreationTimestamp: unversioned.Unix(111, 222), + }, + TypeMeta: common.TypeMeta{Kind: common.ResourceKindJob}, + Pods: common.PodInfo{ + Current: 7, + Desired: 21, + Failed: 1, + Warnings: []common.Event{}, + }, + }}, + }, + nil, + }, + } + + for _, c := range cases { + channels := &common.ResourceChannels{ + JobList: common.JobListChannel{ + List: make(chan *batch.JobList, 1), + Error: make(chan error, 1), + }, + NodeList: common.NodeListChannel{ + List: make(chan *api.NodeList, 1), + Error: make(chan error, 1), + }, + ServiceList: common.ServiceListChannel{ + List: make(chan *api.ServiceList, 1), + Error: make(chan error, 1), + }, + PodList: common.PodListChannel{ + List: make(chan *api.PodList, 1), + Error: make(chan error, 1), + }, + EventList: common.EventListChannel{ + List: make(chan *api.EventList, 1), + Error: make(chan error, 1), + }, + } + + channels.JobList.Error <- c.k8sRsError + channels.JobList.List <- &c.k8sRs + + channels.NodeList.List <- &api.NodeList{} + channels.NodeList.Error <- nil + + channels.ServiceList.List <- &api.ServiceList{} + channels.ServiceList.Error <- nil + + channels.PodList.List <- c.pods + channels.PodList.Error <- nil + + channels.EventList.List <- &api.EventList{} + channels.EventList.Error <- nil + + actual, err := GetJobListFromChannels(channels) + if !reflect.DeepEqual(actual, c.expected) { + t.Errorf("GetJobListChannels() ==\n %#v\nExpected: %#v", actual, c.expected) + } + if !reflect.DeepEqual(err, c.expectedError) { + t.Errorf("GetJobListChannels() ==\n %#v\nExpected: %#v", err, c.expectedError) + } + } +} diff --git a/src/test/backend/resource/workload/workloads_test.go b/src/test/backend/resource/workload/workloads_test.go index 6e50625a55e0..86fc8c9c302f 100644 --- a/src/test/backend/resource/workload/workloads_test.go +++ b/src/test/backend/resource/workload/workloads_test.go @@ -18,23 +18,26 @@ import ( "reflect" "testing" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/extensions" - "github.com/kubernetes/dashboard/resource/common" "github.com/kubernetes/dashboard/resource/daemonset" "github.com/kubernetes/dashboard/resource/deployment" + "github.com/kubernetes/dashboard/resource/job" "github.com/kubernetes/dashboard/resource/petset" "github.com/kubernetes/dashboard/resource/pod" "github.com/kubernetes/dashboard/resource/replicaset" "github.com/kubernetes/dashboard/resource/replicationcontroller" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/apis/extensions" ) func TestGetWorkloadsFromChannels(t *testing.T) { + var jobCompletions int32 = 0 cases := []struct { k8sRs extensions.ReplicaSetList + k8sJobs batch.JobList k8sDaemonSet extensions.DaemonSetList k8sDeployment extensions.DeploymentList k8sRc api.ReplicationControllerList @@ -42,6 +45,7 @@ func TestGetWorkloadsFromChannels(t *testing.T) { k8sPetSet apps.PetSetList rcs []replicationcontroller.ReplicationController rs []replicaset.ReplicaSet + jobs []job.Job daemonset []daemonset.DaemonSet deployment []deployment.Deployment pod []pod.Pod @@ -49,6 +53,7 @@ func TestGetWorkloadsFromChannels(t *testing.T) { }{ { extensions.ReplicaSetList{}, + batch.JobList{}, extensions.DaemonSetList{}, extensions.DeploymentList{}, api.ReplicationControllerList{}, @@ -56,6 +61,7 @@ func TestGetWorkloadsFromChannels(t *testing.T) { apps.PetSetList{}, []replicationcontroller.ReplicationController{}, []replicaset.ReplicaSet{}, + []job.Job{}, []daemonset.DaemonSet{}, []deployment.Deployment{}, []pod.Pod{}, @@ -69,6 +75,16 @@ func TestGetWorkloadsFromChannels(t *testing.T) { Spec: extensions.ReplicaSetSpec{Selector: &unversioned.LabelSelector{}}, }}, }, + batch.JobList{ + Items: []batch.Job{ + { + ObjectMeta: api.ObjectMeta{Name: "job-name"}, + Spec: batch.JobSpec{ + Selector: &unversioned.LabelSelector{}, + Completions: &jobCompletions, + }, + }}, + }, extensions.DaemonSetList{ Items: []extensions.DaemonSet{ { @@ -111,6 +127,15 @@ func TestGetWorkloadsFromChannels(t *testing.T) { Warnings: []common.Event{}, }, }}, + []job.Job{{ + ObjectMeta: common.ObjectMeta{ + Name: "job-name", + }, + TypeMeta: common.TypeMeta{Kind: common.ResourceKindJob}, + Pods: common.PodInfo{ + Warnings: []common.Event{}, + }, + }}, []daemonset.DaemonSet{{ ObjectMeta: common.ObjectMeta{ Name: "ds-name", @@ -142,6 +167,9 @@ func TestGetWorkloadsFromChannels(t *testing.T) { ReplicaSetList: replicaset.ReplicaSetList{ ReplicaSets: c.rs, }, + JobList: job.JobList{ + Jobs: c.jobs, + }, DaemonSetList: daemonset.DaemonSetList{ DaemonSets: c.daemonset, }, @@ -162,6 +190,10 @@ func TestGetWorkloadsFromChannels(t *testing.T) { List: make(chan *extensions.ReplicaSetList, 1), Error: make(chan error, 1), }, + JobList: common.JobListChannel{ + List: make(chan *batch.JobList, 1), + Error: make(chan error, 1), + }, ReplicationControllerList: common.ReplicationControllerListChannel{ List: make(chan *api.ReplicationControllerList, 1), Error: make(chan error, 1), @@ -175,30 +207,33 @@ func TestGetWorkloadsFromChannels(t *testing.T) { Error: make(chan error, 1), }, PetSetList: common.PetSetListChannel{ - List: make(chan *apps.PetSetList, 1), + List: make(chan *apps.PetSetList, 1), Error: make(chan error, 1), }, NodeList: common.NodeListChannel{ - List: make(chan *api.NodeList, 5), - Error: make(chan error, 5), + List: make(chan *api.NodeList, 6), + Error: make(chan error, 6), }, ServiceList: common.ServiceListChannel{ - List: make(chan *api.ServiceList, 5), - Error: make(chan error, 5), + List: make(chan *api.ServiceList, 6), + Error: make(chan error, 6), }, PodList: common.PodListChannel{ - List: make(chan *api.PodList, 6), - Error: make(chan error, 6), + List: make(chan *api.PodList, 7), + Error: make(chan error, 7), }, EventList: common.EventListChannel{ - List: make(chan *api.EventList, 5), - Error: make(chan error, 5), + List: make(chan *api.EventList, 6), + Error: make(chan error, 6), }, } channels.ReplicaSetList.Error <- nil channels.ReplicaSetList.List <- &c.k8sRs + channels.JobList.Error <- nil + channels.JobList.List <- &c.k8sJobs + channels.DaemonSetList.Error <- nil channels.DaemonSetList.List <- &c.k8sDaemonSet @@ -222,6 +257,8 @@ func TestGetWorkloadsFromChannels(t *testing.T) { channels.NodeList.Error <- nil channels.NodeList.List <- nodeList channels.NodeList.Error <- nil + channels.NodeList.List <- nodeList + channels.NodeList.Error <- nil serviceList := &api.ServiceList{} channels.ServiceList.List <- serviceList @@ -234,6 +271,8 @@ func TestGetWorkloadsFromChannels(t *testing.T) { channels.ServiceList.Error <- nil channels.ServiceList.List <- serviceList channels.ServiceList.Error <- nil + channels.ServiceList.List <- serviceList + channels.ServiceList.Error <- nil podList := &c.k8sPod channels.PodList.List <- podList @@ -248,6 +287,8 @@ func TestGetWorkloadsFromChannels(t *testing.T) { channels.PodList.Error <- nil channels.PodList.List <- podList channels.PodList.Error <- nil + channels.PodList.List <- podList + channels.PodList.Error <- nil eventList := &api.EventList{} channels.EventList.List <- eventList @@ -260,6 +301,8 @@ func TestGetWorkloadsFromChannels(t *testing.T) { channels.EventList.Error <- nil channels.EventList.List <- eventList channels.EventList.Error <- nil + channels.EventList.List <- eventList + channels.EventList.Error <- nil actual, err := GetWorkloadsFromChannels(channels, nil) if !reflect.DeepEqual(actual, expected) {