mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-15 14:14:39 +00:00
Pod log subresource
Adds a Log subresource to Pod storage. The Log subresource implements rest.GetterWithOptions and produces a ResourceStreamer resource that will stream the log output from the pod's host node.
This commit is contained in:
@@ -25,10 +25,12 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
||||
genericrest "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/rest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
@@ -40,6 +42,7 @@ type PodStorage struct {
|
||||
Pod *REST
|
||||
Binding *BindingREST
|
||||
Status *StatusREST
|
||||
Log *LogREST
|
||||
}
|
||||
|
||||
// REST implements a RESTStorage for pods against etcd
|
||||
@@ -48,7 +51,7 @@ type REST struct {
|
||||
}
|
||||
|
||||
// NewStorage returns a RESTStorage object that will work against pods.
|
||||
func NewStorage(h tools.EtcdHelper) PodStorage {
|
||||
func NewStorage(h tools.EtcdHelper, k client.ConnectionInfoGetter) PodStorage {
|
||||
prefix := "/registry/pods"
|
||||
store := &etcdgeneric.Etcd{
|
||||
NewFunc: func() runtime.Object { return &api.Pod{} },
|
||||
@@ -85,6 +88,7 @@ func NewStorage(h tools.EtcdHelper) PodStorage {
|
||||
Pod: &REST{*store},
|
||||
Binding: &BindingREST{store: store},
|
||||
Status: &StatusREST{store: &statusStore},
|
||||
Log: &LogREST{store: store, kubeletConn: k},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,3 +190,37 @@ func (r *StatusREST) New() runtime.Object {
|
||||
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||
return r.store.Update(ctx, obj)
|
||||
}
|
||||
|
||||
// LogREST implements the log endpoint for a Pod
|
||||
type LogREST struct {
|
||||
store *etcdgeneric.Etcd
|
||||
kubeletConn client.ConnectionInfoGetter
|
||||
}
|
||||
|
||||
// New creates a new Pod log options object
|
||||
func (r *LogREST) New() runtime.Object {
|
||||
return &api.PodLogOptions{}
|
||||
}
|
||||
|
||||
// Get retrieves a runtime.Object that will stream the contents of the pod log
|
||||
func (r *LogREST) Get(ctx api.Context, name string, opts runtime.Object) (runtime.Object, error) {
|
||||
logOpts, ok := opts.(*api.PodLogOptions)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Invalid options object: %#v", opts)
|
||||
}
|
||||
location, transport, err := pod.LogLocation(r.store, r.kubeletConn, ctx, name, logOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &genericrest.LocationStreamer{
|
||||
Location: location,
|
||||
Transport: transport,
|
||||
ContentType: "text/plain",
|
||||
Flush: logOpts.Follow,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewGetOptions creates a new options object
|
||||
func (r *LogREST) NewGetOptions() runtime.Object {
|
||||
return &api.PodLogOptions{}
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
|
||||
|
||||
func newStorage(t *testing.T) (*REST, *BindingREST, *StatusREST, *tools.FakeEtcdClient, tools.EtcdHelper) {
|
||||
fakeEtcdClient, h := newHelper(t)
|
||||
storage := NewStorage(h)
|
||||
storage := NewStorage(h, nil)
|
||||
return storage.Pod, storage.Binding, storage.Status, fakeEtcdClient, h
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestStorage(t *testing.T) {
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
test := resttest.New(t, storage, fakeEtcdClient.SetError)
|
||||
pod := validNewPod()
|
||||
pod.ObjectMeta = api.ObjectMeta{}
|
||||
@@ -107,7 +107,7 @@ func TestCreate(t *testing.T) {
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
test := resttest.New(t, storage, fakeEtcdClient.SetError)
|
||||
|
||||
createFn := func() runtime.Object {
|
||||
@@ -143,7 +143,7 @@ func expectPod(t *testing.T, out runtime.Object) (*api.Pod, bool) {
|
||||
func TestCreateRegistryError(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.Err = fmt.Errorf("test error")
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
pod := validNewPod()
|
||||
_, err := storage.Create(api.NewDefaultContext(), pod)
|
||||
@@ -154,7 +154,7 @@ func TestCreateRegistryError(t *testing.T) {
|
||||
|
||||
func TestCreateSetsFields(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
pod := validNewPod()
|
||||
_, err := storage.Create(api.NewDefaultContext(), pod)
|
||||
if err != fakeEtcdClient.Err {
|
||||
@@ -176,7 +176,7 @@ func TestCreateSetsFields(t *testing.T) {
|
||||
func TestListError(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.Err = fmt.Errorf("test error")
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
pods, err := storage.List(api.NewDefaultContext(), labels.Everything(), fields.Everything())
|
||||
if err != fakeEtcdClient.Err {
|
||||
t.Fatalf("Expected %#v, Got %#v", fakeEtcdClient.Err, err)
|
||||
@@ -194,7 +194,7 @@ func TestListEmptyPodList(t *testing.T) {
|
||||
E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound),
|
||||
}
|
||||
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
pods, err := storage.List(api.NewContext(), labels.Everything(), fields.Everything())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
@@ -231,7 +231,7 @@ func TestListPodList(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
podsObj, err := storage.List(api.NewDefaultContext(), labels.Everything(), fields.Everything())
|
||||
pods := podsObj.(*api.PodList)
|
||||
@@ -280,7 +280,7 @@ func TestListPodListSelection(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
ctx := api.NewDefaultContext()
|
||||
|
||||
@@ -345,7 +345,7 @@ func TestListPodListSelection(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPodDecode(t *testing.T) {
|
||||
storage := NewStorage(tools.EtcdHelper{}).Pod
|
||||
storage := NewStorage(tools.EtcdHelper{}, nil).Pod
|
||||
expected := validNewPod()
|
||||
body, err := latest.Codec.Encode(expected)
|
||||
if err != nil {
|
||||
@@ -375,7 +375,7 @@ func TestGet(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
obj, err := storage.Get(api.WithNamespace(api.NewContext(), "test"), "foo")
|
||||
pod := obj.(*api.Pod)
|
||||
@@ -392,7 +392,7 @@ func TestGet(t *testing.T) {
|
||||
func TestPodStorageValidatesCreate(t *testing.T) {
|
||||
fakeEtcdClient, helper := newHelper(t)
|
||||
fakeEtcdClient.Err = fmt.Errorf("test error")
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
pod := validNewPod()
|
||||
pod.Labels = map[string]string{
|
||||
@@ -410,7 +410,7 @@ func TestPodStorageValidatesCreate(t *testing.T) {
|
||||
// TODO: remove, this is covered by RESTTest.TestCreate
|
||||
func TestCreatePod(t *testing.T) {
|
||||
_, helper := newHelper(t)
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
pod := validNewPod()
|
||||
obj, err := storage.Create(api.NewDefaultContext(), pod)
|
||||
@@ -432,7 +432,7 @@ func TestCreatePod(t *testing.T) {
|
||||
// TODO: remove, this is covered by RESTTest.TestCreate
|
||||
func TestCreateWithConflictingNamespace(t *testing.T) {
|
||||
_, helper := newHelper(t)
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
pod := validNewPod()
|
||||
pod.Namespace = "not-default"
|
||||
@@ -461,7 +461,7 @@ func TestUpdateWithConflictingNamespace(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
pod := validChangedPod()
|
||||
pod.Namespace = "not-default"
|
||||
@@ -578,7 +578,7 @@ func TestResourceLocation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
redirector := rest.Redirector(storage)
|
||||
location, _, err := redirector.ResourceLocation(api.NewDefaultContext(), tc.query)
|
||||
@@ -616,7 +616,7 @@ func TestDeletePod(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
storage := NewStorage(helper).Pod
|
||||
storage := NewStorage(helper, nil).Pod
|
||||
|
||||
_, err := storage.Delete(api.NewDefaultContext(), "foo", nil)
|
||||
if err != nil {
|
||||
|
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
@@ -133,6 +134,18 @@ type ResourceGetter interface {
|
||||
Get(api.Context, string) (runtime.Object, error)
|
||||
}
|
||||
|
||||
func getPod(getter ResourceGetter, ctx api.Context, name string) (*api.Pod, error) {
|
||||
obj, err := getter.Get(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pod := obj.(*api.Pod)
|
||||
if pod == nil {
|
||||
return nil, fmt.Errorf("Unexpected object type: %#v", pod)
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// ResourceLocation returns a URL to which one can send traffic for the specified pod.
|
||||
func ResourceLocation(getter ResourceGetter, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) {
|
||||
// Allow ID as "podname" or "podname:port". If port is not specified,
|
||||
@@ -148,14 +161,10 @@ func ResourceLocation(getter ResourceGetter, ctx api.Context, id string) (*url.U
|
||||
port = parts[1]
|
||||
}
|
||||
|
||||
obj, err := getter.Get(ctx, name)
|
||||
pod, err := getPod(getter, ctx, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pod := obj.(*api.Pod)
|
||||
if pod == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Try to figure out a port.
|
||||
if port == "" {
|
||||
@@ -177,3 +186,43 @@ func ResourceLocation(getter ResourceGetter, ctx api.Context, id string) (*url.U
|
||||
}
|
||||
return loc, nil, nil
|
||||
}
|
||||
|
||||
// LogLocation returns a the log URL for a pod container. If opts.Container is blank
|
||||
// and only one container is present in the pod, that container is used.
|
||||
func LogLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts *api.PodLogOptions) (*url.URL, http.RoundTripper, error) {
|
||||
|
||||
pod, err := getPod(getter, ctx, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Try to figure out a container
|
||||
container := opts.Container
|
||||
if container == "" {
|
||||
if len(pod.Spec.Containers) == 1 {
|
||||
container = pod.Spec.Containers[0].Name
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("a container name must be specified for pod %s", name)
|
||||
}
|
||||
}
|
||||
nodeHost := pod.Status.HostIP
|
||||
if len(nodeHost) == 0 {
|
||||
// If pod has not been assigned a host, return an empty location
|
||||
return nil, nil, nil
|
||||
}
|
||||
nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
params := url.Values{}
|
||||
if opts.Follow {
|
||||
params.Add("follow", "true")
|
||||
}
|
||||
loc := &url.URL{
|
||||
Scheme: nodeScheme,
|
||||
Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
|
||||
Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, name, container),
|
||||
RawQuery: params.Encode(),
|
||||
}
|
||||
return loc, nodeTransport, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user