Commit 1b46bc01 authored by Maciej Szulik's avatar Maciej Szulik

Job clients, printer and describer

parent c9570e34
......@@ -291,6 +291,7 @@ _kubectl_get()
must_have_one_noun+=("endpoints")
must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("job")
must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace")
must_have_one_noun+=("node")
......@@ -459,6 +460,7 @@ _kubectl_delete()
must_have_one_noun+=("endpoints")
must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("job")
must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace")
must_have_one_noun+=("node")
......@@ -829,6 +831,7 @@ _kubectl_label()
must_have_one_noun+=("endpoints")
must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("job")
must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace")
must_have_one_noun+=("node")
......
......@@ -36,6 +36,7 @@ type ExperimentalInterface interface {
ScaleNamespacer
DaemonSetsNamespacer
DeploymentsNamespacer
JobsNamespacer
}
// ExperimentalClient is used to interact with experimental Kubernetes features.
......@@ -90,6 +91,10 @@ func (c *ExperimentalClient) Deployments(namespace string) DeploymentInterface {
return newDeployments(c, namespace)
}
func (c *ExperimentalClient) Jobs(namespace string) JobInterface {
return newJobs(c, namespace)
}
// NewExperimental creates a new ExperimentalClient for the given config. This client
// provides access to experimental Kubernetes features.
// Experimental features are not supported and may be changed or removed in
......
/*
Copyright 2015 The Kubernetes Authors 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 unversioned
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/experimental"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/watch"
)
// JobsNamespacer has methods to work with Job resources in a namespace
type JobsNamespacer interface {
Jobs(namespace string) JobInterface
}
// JobInterface exposes methods to work on Job resources.
type JobInterface interface {
List(label labels.Selector, field fields.Selector) (*experimental.JobList, error)
Get(name string) (*experimental.Job, error)
Create(job *experimental.Job) (*experimental.Job, error)
Update(job *experimental.Job) (*experimental.Job, error)
Delete(name string, options *api.DeleteOptions) error
Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
UpdateStatus(job *experimental.Job) (*experimental.Job, error)
}
// jobs implements JobsNamespacer interface
type jobs struct {
r *ExperimentalClient
ns string
}
// newJobs returns a jobs
func newJobs(c *ExperimentalClient, namespace string) *jobs {
return &jobs{c, namespace}
}
// List returns a list of jobs that match the label and field selectors.
func (c *jobs) List(label labels.Selector, field fields.Selector) (result *experimental.JobList, err error) {
result = &experimental.JobList{}
err = c.r.Get().Namespace(c.ns).Resource("jobs").LabelsSelectorParam(label).FieldsSelectorParam(field).Do().Into(result)
return
}
// Get returns information about a particular job.
func (c *jobs) Get(name string) (result *experimental.Job, err error) {
result = &experimental.Job{}
err = c.r.Get().Namespace(c.ns).Resource("jobs").Name(name).Do().Into(result)
return
}
// Create creates a new job.
func (c *jobs) Create(job *experimental.Job) (result *experimental.Job, err error) {
result = &experimental.Job{}
err = c.r.Post().Namespace(c.ns).Resource("jobs").Body(job).Do().Into(result)
return
}
// Update updates an existing job.
func (c *jobs) Update(job *experimental.Job) (result *experimental.Job, err error) {
result = &experimental.Job{}
err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).Body(job).Do().Into(result)
return
}
// Delete deletes a job, returns error if one occurs.
func (c *jobs) Delete(name string, options *api.DeleteOptions) (err error) {
if options == nil {
return c.r.Delete().Namespace(c.ns).Resource("jobs").Name(name).Do().Error()
}
body, err := api.Scheme.EncodeToVersion(options, c.r.APIVersion())
if err != nil {
return err
}
return c.r.Delete().Namespace(c.ns).Resource("jobs").Name(name).Body(body).Do().Error()
}
// Watch returns a watch.Interface that watches the requested jobs.
func (c *jobs) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return c.r.Get().
Prefix("watch").
Namespace(c.ns).
Resource("jobs").
Param("resourceVersion", resourceVersion).
LabelsSelectorParam(label).
FieldsSelectorParam(field).
Watch()
}
// UpdateStatus takes the name of the job and the new status. Returns the server's representation of the job, and an error, if it occurs.
func (c *jobs) UpdateStatus(job *experimental.Job) (result *experimental.Job, err error) {
result = &experimental.Job{}
err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).SubResource("status").Body(job).Do().Into(result)
return
}
/*
Copyright 2015 The Kubernetes Authors 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 unversioned
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/experimental"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
)
func getJobResourceName() string {
return "jobs"
}
func TestListJobs(t *testing.T) {
ns := api.NamespaceAll
c := &testClient{
Request: testRequest{
Method: "GET",
Path: testapi.Experimental.ResourcePath(getJobResourceName(), ns, ""),
},
Response: Response{StatusCode: 200,
Body: &experimental.JobList{
Items: []experimental.Job{
{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.JobSpec{
Template: &api.PodTemplateSpec{},
},
},
},
},
},
}
receivedJobList, err := c.Setup(t).Experimental().Jobs(ns).List(labels.Everything(), fields.Everything())
c.Validate(t, receivedJobList, err)
}
func TestGetJob(t *testing.T) {
ns := api.NamespaceDefault
c := &testClient{
Request: testRequest{
Method: "GET",
Path: testapi.Experimental.ResourcePath(getJobResourceName(), ns, "foo"),
Query: buildQueryValues(nil),
},
Response: Response{
StatusCode: 200,
Body: &experimental.Job{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.JobSpec{
Template: &api.PodTemplateSpec{},
},
},
},
}
receivedJob, err := c.Setup(t).Experimental().Jobs(ns).Get("foo")
c.Validate(t, receivedJob, err)
}
func TestGetJobWithNoName(t *testing.T) {
ns := api.NamespaceDefault
c := &testClient{Error: true}
receivedJob, err := c.Setup(t).Experimental().Jobs(ns).Get("")
if (err != nil) && (err.Error() != nameRequiredError) {
t.Errorf("Expected error: %v, but got %v", nameRequiredError, err)
}
c.Validate(t, receivedJob, err)
}
func TestUpdateJob(t *testing.T) {
ns := api.NamespaceDefault
requestJob := &experimental.Job{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: ns,
ResourceVersion: "1",
},
}
c := &testClient{
Request: testRequest{
Method: "PUT",
Path: testapi.Experimental.ResourcePath(getJobResourceName(), ns, "foo"),
Query: buildQueryValues(nil),
},
Response: Response{
StatusCode: 200,
Body: &experimental.Job{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.JobSpec{
Template: &api.PodTemplateSpec{},
},
},
},
}
receivedJob, err := c.Setup(t).Experimental().Jobs(ns).Update(requestJob)
c.Validate(t, receivedJob, err)
}
func TestUpdateJobStatus(t *testing.T) {
ns := api.NamespaceDefault
requestJob := &experimental.Job{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: ns,
ResourceVersion: "1",
},
}
c := &testClient{
Request: testRequest{
Method: "PUT",
Path: testapi.Experimental.ResourcePath(getJobResourceName(), ns, "foo") + "/status",
Query: buildQueryValues(nil),
},
Response: Response{
StatusCode: 200,
Body: &experimental.Job{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.JobSpec{
Template: &api.PodTemplateSpec{},
},
Status: experimental.JobStatus{
Active: 1,
},
},
},
}
receivedJob, err := c.Setup(t).Experimental().Jobs(ns).UpdateStatus(requestJob)
c.Validate(t, receivedJob, err)
}
func TestDeleteJob(t *testing.T) {
ns := api.NamespaceDefault
c := &testClient{
Request: testRequest{
Method: "DELETE",
Path: testapi.Experimental.ResourcePath(getJobResourceName(), ns, "foo"),
Query: buildQueryValues(nil),
},
Response: Response{StatusCode: 200},
}
err := c.Setup(t).Experimental().Jobs(ns).Delete("foo", nil)
c.Validate(t, nil, err)
}
func TestCreateJob(t *testing.T) {
ns := api.NamespaceDefault
requestJob := &experimental.Job{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: ns,
},
}
c := &testClient{
Request: testRequest{
Method: "POST",
Path: testapi.Experimental.ResourcePath(getJobResourceName(), ns, ""),
Body: requestJob,
Query: buildQueryValues(nil),
},
Response: Response{
StatusCode: 200,
Body: &experimental.Job{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: experimental.JobSpec{
Template: &api.PodTemplateSpec{},
},
},
},
}
receivedJob, err := c.Setup(t).Experimental().Jobs(ns).Create(requestJob)
c.Validate(t, receivedJob, err)
}
......@@ -261,3 +261,7 @@ func (c *FakeExperimental) Deployments(namespace string) client.DeploymentInterf
func (c *FakeExperimental) Scales(namespace string) client.ScaleInterface {
return &FakeScales{Fake: c, Namespace: namespace}
}
func (c *FakeExperimental) Jobs(namespace string) client.JobInterface {
panic("unimplemented")
}
......@@ -28,6 +28,7 @@ import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/apis/experimental"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fieldpath"
"k8s.io/kubernetes/pkg/fields"
......@@ -85,6 +86,7 @@ func describerMap(c *client.Client) map[string]Describer {
func expDescriberMap(c *client.Client) map[string]Describer {
return map[string]Describer{
"HorizontalPodAutoscaler": &HorizontalPodAutoscalerDescriber{c},
"Job": &JobDescriber{c},
}
}
......@@ -854,6 +856,46 @@ func describeReplicationController(controller *api.ReplicationController, events
})
}
// JobDescriber generates information about a job and the pods it has created.
type JobDescriber struct {
client *client.Client
}
func (d *JobDescriber) Describe(namespace, name string) (string, error) {
job, err := d.client.Experimental().Jobs(namespace).Get(name)
if err != nil {
return "", err
}
events, _ := d.client.Events(namespace).Search(job)
return describeJob(job, events)
}
func describeJob(job *experimental.Job, events *api.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "Name:\t%s\n", job.Name)
fmt.Fprintf(out, "Namespace:\t%s\n", job.Namespace)
if job.Spec.Template != nil {
fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&job.Spec.Template.Spec))
} else {
fmt.Fprintf(out, "Image(s):\t%s\n", "<no template>")
}
fmt.Fprintf(out, "Selector:\t%s\n", labels.FormatLabels(job.Spec.Selector))
fmt.Fprintf(out, "Parallelism:\t%d\n", job.Spec.Parallelism)
fmt.Fprintf(out, "Completions:\t%d\n", job.Spec.Completions)
fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(job.Labels))
fmt.Fprintf(out, "Pods Statuses:\t%d Running / %d Succeeded / %d Failed\n", job.Status.Active, job.Status.Successful, job.Status.Unsuccessful)
if job.Spec.Template != nil {
describeVolumes(job.Spec.Template.Spec.Volumes, out)
}
if events != nil {
DescribeEvents(events, out)
}
return nil
})
}
// SecretDescriber generates information about a secret
type SecretDescriber struct {
client.Interface
......
......@@ -380,6 +380,7 @@ func (h *HumanReadablePrinter) HandledResources() []string {
var podColumns = []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}
var podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"}
var replicationControllerColumns = []string{"CONTROLLER", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "REPLICAS", "AGE"}
var jobColumns = []string{"JOB", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "SUCCESSFUL"}
var serviceColumns = []string{"NAME", "CLUSTER_IP", "EXTERNAL_IP", "PORT(S)", "SELECTOR", "AGE"}
var endpointColumns = []string{"NAME", "ENDPOINTS", "AGE"}
var nodeColumns = []string{"NAME", "LABELS", "STATUS", "AGE"}
......@@ -405,6 +406,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(podTemplateColumns, printPodTemplateList)
h.Handler(replicationControllerColumns, printReplicationController)
h.Handler(replicationControllerColumns, printReplicationControllerList)
h.Handler(jobColumns, printJob)
h.Handler(jobColumns, printJobList)
h.Handler(serviceColumns, printService)
h.Handler(serviceColumns, printServiceList)
h.Handler(endpointColumns, printEndpoints)
......@@ -655,7 +658,6 @@ func printPodTemplateList(podList *api.PodTemplateList, w io.Writer, withNamespa
func printReplicationController(controller *api.ReplicationController, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error {
name := controller.Name
namespace := controller.Namespace
containers := controller.Spec.Template.Spec.Containers
var firstContainer api.Container
if len(containers) > 0 {
......@@ -707,6 +709,33 @@ func printReplicationControllerList(list *api.ReplicationControllerList, w io.Wr
return nil
}
func printJob(job *experimental.Job, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error {
containers := job.Spec.Template.Spec.Containers
var firstContainer api.Container
if len(containers) > 0 {
firstContainer = containers[0]
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n",
job.Name,
firstContainer.Name,
firstContainer.Image,
labels.FormatLabels(job.Spec.Selector),
job.Status.Successful)
if err != nil {
return err
}
return nil
}
func printJobList(list *experimental.JobList, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error {
for _, job := range list.Items {
if err := printJob(&job, w, withNamespace, wide, showAll, columnLabels); err != nil {
return err
}
}
return nil
}
func getServiceExternalIP(svc *api.Service) string {
switch svc.Spec.Type {
case api.ServiceTypeClusterIP:
......
......@@ -14,6 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package controller provides Registry interface and it's RESTStorage
// Package job provides Registry interface and it's RESTStorage
// implementation for storing Job api objects.
package job
......@@ -28,7 +28,7 @@ import (
"k8s.io/kubernetes/pkg/storage"
)
// rest implements a RESTStorage for jobs against etcd
// REST implements a RESTStorage for jobs against etcd
type REST struct {
*etcdgeneric.Etcd
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment