Skip to content

Commit bc3c542

Browse files
committed
Add Job resource backend (#792)
1 parent 39c7d30 commit bc3c542

23 files changed

+1005
-32
lines changed

i18n/messages-en.xtb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,6 @@
142142
<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>
143143
<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>
144144
<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>
145+
<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>
146+
<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>
145147
</translationbundle>

i18n/messages-ja.xtb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,6 @@
142142
<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>
143143
<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>
144144
<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>
145+
<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>
146+
<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>
145147
</translationbundle>

src/app/backend/handler/apihandler.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
. "github.com/kubernetes/dashboard/resource/container"
2929
"github.com/kubernetes/dashboard/resource/daemonset"
3030
"github.com/kubernetes/dashboard/resource/deployment"
31+
"github.com/kubernetes/dashboard/resource/job"
3132
. "github.com/kubernetes/dashboard/resource/namespace"
3233
"github.com/kubernetes/dashboard/resource/petset"
3334
"github.com/kubernetes/dashboard/resource/pod"
@@ -88,7 +89,7 @@ func CreateHttpApiHandler(client *client.Client, heapsterClient HeapsterClient,
8889
clientConfig clientcmd.ClientConfig) http.Handler {
8990

9091
verber := common.NewResourceVerber(client.RESTClient, client.ExtensionsClient.RESTClient,
91-
client.AppsClient.RESTClient)
92+
client.AppsClient.RESTClient, client.BatchClient.RESTClient)
9293
apiHandler := ApiHandler{client, heapsterClient, clientConfig, verber}
9394
wsContainer := restful.NewContainer()
9495

@@ -218,6 +219,19 @@ func CreateHttpApiHandler(client *client.Client, heapsterClient HeapsterClient,
218219
apiV1Ws.DELETE("/daemonset/{namespace}/{daemonSet}").
219220
To(apiHandler.handleDeleteDaemonSet))
220221

222+
apiV1Ws.Route(
223+
apiV1Ws.GET("/job").
224+
To(apiHandler.handleGetJobs).
225+
Writes(job.JobList{}))
226+
apiV1Ws.Route(
227+
apiV1Ws.GET("/job/{namespace}").
228+
To(apiHandler.handleGetJobs).
229+
Writes(job.JobList{}))
230+
apiV1Ws.Route(
231+
apiV1Ws.GET("/job/{namespace}/{job}").
232+
To(apiHandler.handleGetJobDetail).
233+
Writes(job.JobDetail{}))
234+
221235
apiV1Ws.Route(
222236
apiV1Ws.POST("/namespace").
223237
To(apiHandler.handleCreateNamespace).
@@ -756,6 +770,32 @@ func (apiHandler *ApiHandler) handleDeleteDaemonSet(
756770
response.WriteHeader(http.StatusOK)
757771
}
758772

773+
// Handles get Jobs list API call.
774+
func (apiHandler *ApiHandler) handleGetJobs(request *restful.Request, response *restful.Response) {
775+
namespace := parseNamespacePathParameter(request)
776+
777+
result, err := job.GetJobList(apiHandler.client, namespace)
778+
if err != nil {
779+
handleInternalError(response, err)
780+
return
781+
}
782+
783+
response.WriteHeaderAndEntity(http.StatusCreated, result)
784+
}
785+
786+
func (apiHandler *ApiHandler) handleGetJobDetail(request *restful.Request, response *restful.Response) {
787+
namespace := request.PathParameter("namespace")
788+
jobParam := request.PathParameter("job")
789+
790+
result, err := job.GetJobDetail(apiHandler.client, apiHandler.heapsterClient, namespace, jobParam)
791+
if err != nil {
792+
handleInternalError(response, err)
793+
return
794+
}
795+
796+
response.WriteHeaderAndEntity(http.StatusCreated, result)
797+
}
798+
759799
// parseNamespacePathParameter parses namespace selector for list pages in path paramater.
760800
// The namespace selector is a comma separated list of namespaces that are trimmed.
761801
// No namespaces means "view all user namespaces", i.e., everything except kube-system.

src/app/backend/resource/common/resourcechannels.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package common
1717
import (
1818
"k8s.io/kubernetes/pkg/api"
1919
"k8s.io/kubernetes/pkg/apis/apps"
20+
"k8s.io/kubernetes/pkg/apis/batch"
2021
"k8s.io/kubernetes/pkg/apis/extensions"
2122
client "k8s.io/kubernetes/pkg/client/unversioned"
2223
"k8s.io/kubernetes/pkg/fields"
@@ -47,6 +48,9 @@ type ResourceChannels struct {
4748
// List and error channels to Daemon Sets.
4849
DaemonSetList DaemonSetListChannel
4950

51+
// List and error channels to Jobs.
52+
JobList JobListChannel
53+
5054
// List and error channels to Services.
5155
ServiceList ServiceListChannel
5256

@@ -343,6 +347,39 @@ func GetDaemonSetListChannel(client client.DaemonSetsNamespacer,
343347
return channel
344348
}
345349

350+
// JobListChannel is a list and error channels to Nodes.
351+
type JobListChannel struct {
352+
List chan *batch.JobList
353+
Error chan error
354+
}
355+
356+
// GetJobListChannel returns a pair of channels to a Job list and errors that
357+
// both must be read numReads times.
358+
func GetJobListChannel(client client.JobsNamespacer,
359+
nsQuery *NamespaceQuery, numReads int) JobListChannel {
360+
channel := JobListChannel{
361+
List: make(chan *batch.JobList, numReads),
362+
Error: make(chan error, numReads),
363+
}
364+
365+
go func() {
366+
list, err := client.Jobs(nsQuery.ToRequestParam()).List(listEverything)
367+
var filteredItems []batch.Job
368+
for _, item := range list.Items {
369+
if nsQuery.Matches(item.ObjectMeta.Namespace) {
370+
filteredItems = append(filteredItems, item)
371+
}
372+
}
373+
list.Items = filteredItems
374+
for i := 0; i < numReads; i++ {
375+
channel.List <- list
376+
channel.Error <- err
377+
}
378+
}()
379+
380+
return channel
381+
}
382+
346383
// PetSetListChannel is a list and error channels to Nodes.
347384
type PetSetListChannel struct {
348385
List chan *apps.PetSetList

src/app/backend/resource/common/types.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const (
9595
ResourceKindEvent = "event"
9696
ResourceKindReplicationController = "replicationcontroller"
9797
ResourceKindDaemonSet = "daemonset"
98+
ResourceKindJob = "job"
9899
ResourceKindPetSet = "petset"
99100
)
100101

@@ -105,9 +106,10 @@ type ClientType string
105106

106107
// List of client types supported by the UI.
107108
const (
108-
ClientTypeDefault = "restclient"
109+
ClientTypeDefault = "restclient"
109110
ClientTypeExtensionClient = "extensionclient"
110-
ClientTypeAppsClient = "appsclient"
111+
ClientTypeAppsClient = "appsclient"
112+
ClientTypeBatchClient = "batchclient"
111113
)
112114

113115
// Mapping from resource kind to K8s apiserver API path. This is mostly pluralization, because
@@ -128,6 +130,7 @@ var kindToAPIMapping = map[string]struct {
128130
ResourceKindReplicaSet: {"replicasets", ClientTypeExtensionClient},
129131
ResourceKindDaemonSet: {"daemonsets", ClientTypeDefault},
130132
ResourceKindPetSet: {"petsets", ClientTypeAppsClient},
133+
ResourceKindJob: {"jobs", ClientTypeBatchClient},
131134
}
132135

133136
// IsSelectorMatching returns true when an object with the given

src/app/backend/resource/common/verber.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type ResourceVerber struct {
2626
client RESTClient
2727
extensionsClient RESTClient
2828
appsClient RESTClient
29+
batchClient RESTClient
2930
}
3031

3132
// RESTClient is an interface for REST operations used in this file.
@@ -35,8 +36,9 @@ type RESTClient interface {
3536

3637
// NewResourceVerber creates a new resource verber that uses the given client for performing
3738
// operations.
38-
func NewResourceVerber(client, extensionsClient, appsClient RESTClient) ResourceVerber {
39-
return ResourceVerber{client, extensionsClient, appsClient}
39+
func NewResourceVerber(client, extensionsClient, appsClient,
40+
batchClient RESTClient) ResourceVerber {
41+
return ResourceVerber{client, extensionsClient, appsClient, batchClient}
4042
}
4143

4244
// 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
6264
return verber.extensionsClient
6365
case ClientTypeAppsClient:
6466
return verber.appsClient
67+
case ClientTypeBatchClient:
68+
return verber.batchClient
6569
default:
6670
return verber.client
6771
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2015 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package job
16+
17+
import (
18+
"github.com/kubernetes/dashboard/resource/common"
19+
"k8s.io/kubernetes/pkg/api"
20+
"k8s.io/kubernetes/pkg/apis/batch"
21+
)
22+
23+
// getPodInfo returns aggregate information about job pods.
24+
func getPodInfo(resource *batch.Job, pods []api.Pod) common.PodInfo {
25+
return common.GetPodInfo(resource.Status.Active, *resource.Spec.Completions, pods)
26+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2015 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package job
16+
17+
import (
18+
"log"
19+
20+
"github.com/kubernetes/dashboard/client"
21+
"github.com/kubernetes/dashboard/resource/common"
22+
"github.com/kubernetes/dashboard/resource/pod"
23+
"k8s.io/kubernetes/pkg/api"
24+
"k8s.io/kubernetes/pkg/apis/batch"
25+
k8sClient "k8s.io/kubernetes/pkg/client/unversioned"
26+
)
27+
28+
// JobDetail is a presentation layer view of Kubernetes Job resource. This means
29+
// it is Job plus additional augmented data we can get from other sources
30+
// (like services that target the same pods).
31+
type JobDetail struct {
32+
ObjectMeta common.ObjectMeta `json:"objectMeta"`
33+
TypeMeta common.TypeMeta `json:"typeMeta"`
34+
35+
// Aggregate information about pods belonging to this Job.
36+
PodInfo common.PodInfo `json:"podInfo"`
37+
38+
// Detailed information about Pods belonging to this Job.
39+
PodList pod.PodList `json:"podList"`
40+
41+
// Container images of the Job.
42+
ContainerImages []string `json:"containerImages"`
43+
44+
// List of events related to this Job.
45+
EventList common.EventList `json:"eventList"`
46+
}
47+
48+
// GetJobDetail gets job details.
49+
func GetJobDetail(client k8sClient.Interface, heapsterClient client.HeapsterClient,
50+
namespace, name string) (*JobDetail, error) {
51+
52+
log.Printf("Getting details of %s service in %s namespace", name, namespace)
53+
54+
// TODO(floreks): Use channels.
55+
jobData, err := client.Extensions().Jobs(namespace).Get(name)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
channels := &common.ResourceChannels{
61+
PodList: common.GetPodListChannel(client, common.NewSameNamespaceQuery(namespace), 1),
62+
}
63+
64+
pods := <-channels.PodList.List
65+
if err := <-channels.PodList.Error; err != nil {
66+
return nil, err
67+
}
68+
69+
events, err := GetJobEvents(client, jobData.Namespace, jobData.Name)
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
job := getJobDetail(jobData, heapsterClient, events, pods.Items)
75+
return &job, nil
76+
}
77+
78+
func getJobDetail(job *batch.Job, heapsterClient client.HeapsterClient,
79+
events *common.EventList, pods []api.Pod) JobDetail {
80+
81+
matchingPods := common.FilterNamespacedPodsBySelector(pods, job.ObjectMeta.Namespace,
82+
job.Spec.Selector.MatchLabels)
83+
84+
podInfo := getPodInfo(job, matchingPods)
85+
86+
return JobDetail{
87+
ObjectMeta: common.NewObjectMeta(job.ObjectMeta),
88+
TypeMeta: common.NewTypeMeta(common.ResourceKindJob),
89+
ContainerImages: common.GetContainerImages(&job.Spec.Template.Spec),
90+
PodInfo: podInfo,
91+
PodList: pod.CreatePodList(matchingPods, heapsterClient),
92+
EventList: *events,
93+
}
94+
}

0 commit comments

Comments
 (0)