Allow getting logs directly from deployment, job and statefulset

This commit is contained in:
Maciej Szulik
2017-02-16 12:46:54 +01:00
parent 27259358cb
commit 5472a5e0a5
4 changed files with 253 additions and 38 deletions

View File

@@ -54,13 +54,19 @@ var (
kubectl logs --tail=20 nginx
# Show all logs from pod nginx written in the last hour
kubectl logs --since=1h nginx`)
kubectl logs --since=1h nginx
# Return snapshot logs from first container of a job named hello
kubectl logs job/hello
# Return snapshot logs from container nginx-1 of a deployment named nginx
kubectl logs deployment/nginx -c nginx-1`)
selectorTail int64 = 10
)
const (
logsUsageStr = "expected 'logs POD_NAME [CONTAINER_NAME]'.\nPOD_NAME is a required argument for the logs command"
logsUsageStr = "expected 'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command"
)
type LogsOptions struct {
@@ -83,9 +89,9 @@ type LogsOptions struct {
func NewCmdLogs(f cmdutil.Factory, out io.Writer) *cobra.Command {
o := &LogsOptions{}
cmd := &cobra.Command{
Use: "logs [-f] [-p] POD [-c CONTAINER]",
Use: "logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER]",
Short: i18n.T("Print the logs for a container in a pod"),
Long: "Print the logs for a container in a pod. If the pod has only one container, the container name is optional.",
Long: "Print the logs for a container in a pod or specified resource. If the pod has only one container, the container name is optional.",
Example: logs_example,
PreRun: func(cmd *cobra.Command, args []string) {
if len(os.Args) > 1 && os.Args[1] == "log" {
@@ -94,9 +100,7 @@ func NewCmdLogs(f cmdutil.Factory, out io.Writer) *cobra.Command {
},
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, out, cmd, args))
if err := o.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunLogs())
},
Aliases: []string{"log"},

View File

@@ -75,6 +75,7 @@ go_test(
name = "go_default_test",
srcs = [
"cached_discovery_test.go",
"factory_object_mapping_test.go",
"factory_test.go",
"helpers_test.go",
"shortcut_restmapper_test.go",
@@ -90,7 +91,10 @@ go_test(
"//pkg/api/testing:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library",
@@ -104,6 +108,7 @@ go_test(
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/apimachinery/pkg/version",
"//vendor:k8s.io/apimachinery/pkg/watch",

View File

@@ -213,51 +213,48 @@ func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclien
if err != nil {
return nil, err
}
opts, ok := options.(*api.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
var selector labels.Selector
var namespace string
switch t := object.(type) {
case *api.Pod:
opts, ok := options.(*api.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
return clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil
case *api.ReplicationController:
opts, ok := options.(*api.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
selector := labels.SelectorFromSet(t.Spec.Selector)
sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }
pod, numPods, err := GetFirstPod(clientset.Core(), t.Namespace, selector, 20*time.Second, sortBy)
if err != nil {
return nil, err
}
if numPods > 1 {
fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
}
return clientset.Core().Pods(pod.Namespace).GetLogs(pod.Name, opts), nil
namespace = t.Namespace
selector = labels.SelectorFromSet(t.Spec.Selector)
case *extensions.ReplicaSet:
opts, ok := options.(*api.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
selector, err := metav1.LabelSelectorAsSelector(t.Spec.Selector)
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }
pod, numPods, err := GetFirstPod(clientset.Core(), t.Namespace, selector, 20*time.Second, sortBy)
case *extensions.Deployment:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, err
}
if numPods > 1 {
fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
return nil, fmt.Errorf("invalid label selector: %v", err)
}
return clientset.Core().Pods(pod.Namespace).GetLogs(pod.Name, opts), nil
case *batch.Job:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
case *apps.StatefulSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
default:
gvks, _, err := api.Scheme.ObjectKinds(object)
@@ -266,6 +263,16 @@ func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclien
}
return nil, fmt.Errorf("cannot get the logs from %v", gvks[0])
}
sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }
pod, numPods, err := GetFirstPod(clientset.Core(), namespace, selector, 20*time.Second, sortBy)
if err != nil {
return nil, err
}
if numPods > 1 {
fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
}
return clientset.Core().Pods(pod.Namespace).GetLogs(pod.Name, opts), nil
}
func (f *ring1Factory) Scaler(mapping *meta.RESTMapping) (kubectl.Scaler, error) {
@@ -329,29 +336,35 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object) (*api.Pod,
case *extensions.ReplicaSet:
namespace = t.Namespace
selector = labels.SelectorFromSet(t.Spec.Selector.MatchLabels)
case *api.ReplicationController:
namespace = t.Namespace
selector = labels.SelectorFromSet(t.Spec.Selector)
case *apps.StatefulSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
case *extensions.Deployment:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
case *batch.Job:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
case *api.Pod:
return t, nil
default:
gvks, _, err := api.Scheme.ObjectKinds(object)
if err != nil {
@@ -359,6 +372,7 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object) (*api.Pod,
}
return nil, fmt.Errorf("cannot attach to %v: not implemented", gvks[0])
}
sortBy := func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }
pod, _, err := GetFirstPod(clientset.Core(), namespace, selector, 1*time.Minute, sortBy)
return pod, err

View File

@@ -0,0 +1,192 @@
/*
Copyright 2017 The Kubernetes Authors.
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 util
import (
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
testclient "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
)
type fakeClientAccessFactory struct {
ClientAccessFactory
fakeClientset *fake.Clientset
}
func (f *fakeClientAccessFactory) ClientSetForVersion(requiredVersion *schema.GroupVersion) (internalclientset.Interface, error) {
return f.fakeClientset, nil
}
func newFakeClientAccessFactory(objs []runtime.Object) *fakeClientAccessFactory {
return &fakeClientAccessFactory{
fakeClientset: fake.NewSimpleClientset(objs...),
}
}
var (
podsResource = schema.GroupVersionResource{Resource: "pods"}
)
func TestLogsForObject(t *testing.T) {
tests := []struct {
name string
obj runtime.Object
opts *api.PodLogOptions
pods []runtime.Object
actions []testclient.Action
}{
{
name: "pod logs",
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
getLogsAction("test", nil),
},
},
{
name: "replication controller logs",
obj: &api.ReplicationController{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{"foo": "bar"},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "replica set logs",
obj: &extensions.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: extensions.ReplicaSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "deployment logs",
obj: &extensions.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: extensions.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "job logs",
obj: &batch.Job{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: batch.JobSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "stateful set logs",
obj: &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: apps.StatefulSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
}
for _, test := range tests {
caf := newFakeClientAccessFactory(test.pods)
omf := NewObjectMappingFactory(caf)
_, err := omf.LogsForObject(test.obj, test.opts)
if err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
continue
}
for i := range test.actions {
if len(caf.fakeClientset.Actions()) < i {
t.Errorf("%s: action %d does not exists in actual actions: %#v",
test.name, i, caf.fakeClientset.Actions())
continue
}
got := caf.fakeClientset.Actions()[i]
want := test.actions[i]
if !reflect.DeepEqual(got, want) {
t.Errorf("%s: unexpected action: %s", test.name, diff.ObjectDiff(got, want))
}
}
}
}
func testPod() runtime.Object {
return &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "test",
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "c1"}},
},
}
}
func getLogsAction(namespace string, opts *api.PodLogOptions) testclient.Action {
action := testclient.GenericActionImpl{}
action.Verb = "get"
action.Namespace = namespace
action.Resource = podsResource
action.Subresource = "logs"
action.Value = opts
return action
}