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) {