Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions i18n/messages-en.xtb
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,6 @@
<translation id="5744665687208066898" key="MSG_PET_SET_LIST_HEADER_IMAGES" source="/home/floreks/Projects/dashboard/.tmp/serve/app-dev.js" desc="Pet set list header: images.">Images</translation>
<translation id="2997495891426796243" key="MSG_NAMESPACE_NOT_SELECTED" source="/usr/local/google/home/bryk/src/github.com/dashboard/.tmp/serve/app-dev.js" desc="Text for dropdown item that indicates that no namespace was selected">namespace not selected</translation>
<translation id="5143266087600531180" key="MSG_NAMESPACE_SELECT_ARIA_LABEL" source="/usr/local/google/home/bryk/src/github.com/dashboard/.tmp/serve/app-dev.js" desc="Text describing what namespace selector is">Selector for namespaces</translation>
<translation id="7612562063769289643" key="MSG_PODS_ARE_FAILED_TOOLTIP" source="/home/maciaszczykm/workspace/dashboard/.tmp/serve/app-dev.js" desc="tooltip for failed pod card icon">One or more pods have errors.</translation>
<translation id="7471636163011126552" key="MSG_PODS_ARE_PENDING_TOOLTIP" source="/home/maciaszczykm/workspace/dashboard/.tmp/serve/app-dev.js" desc="tooltip for pending pod card icon">One or more pods are in pending state.</translation>
</translationbundle>
2 changes: 2 additions & 0 deletions i18n/messages-ja.xtb
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,6 @@
<translation id="5744665687208066898" key="MSG_PET_SET_LIST_HEADER_IMAGES" source="/home/floreks/Projects/dashboard/.tmp/serve/app-dev.js" desc="Pet set list header: images.">Images</translation>
<translation id="2997495891426796243" key="MSG_NAMESPACE_NOT_SELECTED" source="/usr/local/google/home/bryk/src/github.com/dashboard/.tmp/serve/app-dev.js" desc="Text for dropdown item that indicates that no namespace was selected">namespace not selected</translation>
<translation id="5143266087600531180" key="MSG_NAMESPACE_SELECT_ARIA_LABEL" source="/usr/local/google/home/bryk/src/github.com/dashboard/.tmp/serve/app-dev.js" desc="Text describing what namespace selector is">Selector for namespaces</translation>
<translation id="7612562063769289643" key="MSG_PODS_ARE_FAILED_TOOLTIP" source="/home/maciaszczykm/workspace/dashboard/.tmp/serve/app-dev.js" desc="tooltip for failed pod card icon">One or more pods have errors.</translation>
<translation id="7471636163011126552" key="MSG_PODS_ARE_PENDING_TOOLTIP" source="/home/maciaszczykm/workspace/dashboard/.tmp/serve/app-dev.js" desc="tooltip for pending pod card icon">One or more pods are in pending state.</translation>
</translationbundle>
42 changes: 41 additions & 1 deletion src/app/backend/handler/apihandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions src/app/backend/resource/common/resourcechannels.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions src/app/backend/resource/common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const (
ResourceKindEvent = "event"
ResourceKindReplicationController = "replicationcontroller"
ResourceKindDaemonSet = "daemonset"
ResourceKindJob = "job"
ResourceKindPetSet = "petset"
)

Expand All @@ -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
Expand All @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/app/backend/resource/common/verber.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
}
Expand Down
26 changes: 26 additions & 0 deletions src/app/backend/resource/job/jobcommon.go
Original file line number Diff line number Diff line change
@@ -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)
}
94 changes: 94 additions & 0 deletions src/app/backend/resource/job/jobdetail.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
Loading