From 91bddd13485082892be8e8e471e358be317c4e9b Mon Sep 17 00:00:00 2001 From: Jianfei Bai Date: Thu, 10 Oct 2019 21:30:05 +0800 Subject: [PATCH] feat(apiserver): add user-agent and remote info into trace log for endpoints handlers. --- .../apiserver/pkg/endpoints/handlers/BUILD | 4 ++ .../pkg/endpoints/handlers/create.go | 2 +- .../pkg/endpoints/handlers/delete.go | 2 +- .../apiserver/pkg/endpoints/handlers/get.go | 4 +- .../pkg/endpoints/handlers/helpers.go | 60 +++++++++++++++++++ .../pkg/endpoints/handlers/helpers_test.go | 59 ++++++++++++++++++ .../apiserver/pkg/endpoints/handlers/patch.go | 2 +- .../pkg/endpoints/handlers/update.go | 2 +- 8 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/helpers.go create mode 100644 staging/src/k8s.io/apiserver/pkg/endpoints/handlers/helpers_test.go diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD index 06cf15e8c0d..a9eb231f007 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD @@ -10,6 +10,7 @@ go_test( name = "go_default_test", srcs = [ "create_test.go", + "helpers_test.go", "namer_test.go", "rest_test.go", ], @@ -38,6 +39,7 @@ go_test( "//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library", "//vendor/github.com/evanphx/json-patch:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/utils/trace:go_default_library", ], ) @@ -49,6 +51,7 @@ go_library( "delete.go", "doc.go", "get.go", + "helpers.go", "namer.go", "patch.go", "response.go", @@ -74,6 +77,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go index e01a6d97836..fa0d4a24120 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go @@ -46,7 +46,7 @@ import ( func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { // For performance tracking purposes. - trace := utiltrace.New("Create", utiltrace.Field{"url", req.URL.Path}) + trace := utiltrace.New("Create", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}}) defer trace.LogIfLong(500 * time.Millisecond) if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go index 4e1f1e8354d..b1b0ad95b98 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go @@ -45,7 +45,7 @@ import ( func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestScope, admit admission.Interface) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { // For performance tracking purposes. - trace := utiltrace.New("Delete", utiltrace.Field{"url", req.URL.Path}) + trace := utiltrace.New("Delete", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}}) defer trace.LogIfLong(500 * time.Millisecond) if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go index 9a0e0e26e83..3c51318e8ca 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go @@ -49,7 +49,7 @@ type getterFunc func(ctx context.Context, name string, req *http.Request, trace // passed-in getterFunc to perform the actual get. func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { - trace := utiltrace.New("Get", utiltrace.Field{"url", req.URL.Path}) + trace := utiltrace.New("Get", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}}) defer trace.LogIfLong(500 * time.Millisecond) namespace, name, err := scope.Namer.Name(req) @@ -166,7 +166,7 @@ func getRequestOptions(req *http.Request, scope *RequestScope, into runtime.Obje func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { // For performance tracking purposes. - trace := utiltrace.New("List", utiltrace.Field{"url", req.URL.Path}) + trace := utiltrace.New("List", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}}) namespace, err := scope.Namer.Namespace(req) if err != nil { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/helpers.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/helpers.go new file mode 100644 index 00000000000..82170e050ec --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/helpers.go @@ -0,0 +1,60 @@ +/* +Copyright 2019 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 handlers + +import ( + "net/http" + + utilnet "k8s.io/apimachinery/pkg/util/net" +) + +const ( + maxUserAgentLength = 1024 + userAgentTruncateSuffix = "...TRUNCATED" +) + +// lazyTruncatedUserAgent implements String() string and it will +// return user-agent which may be truncated. +type lazyTruncatedUserAgent struct { + req *http.Request +} + +func (lazy *lazyTruncatedUserAgent) String() string { + ua := "unknown" + if lazy.req != nil { + ua = utilnet.GetHTTPClient(lazy.req) + if len(ua) > maxUserAgentLength { + ua = ua[:maxUserAgentLength] + userAgentTruncateSuffix + } + } + return ua +} + +// LazyClientIP implements String() string and it will +// calls GetClientIP() lazily only when required. +type lazyClientIP struct { + req *http.Request +} + +func (lazy *lazyClientIP) String() string { + if lazy.req != nil { + if ip := utilnet.GetClientIP(lazy.req); ip != nil { + return ip.String() + } + } + return "unknown" +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/helpers_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/helpers_test.go new file mode 100644 index 00000000000..e55b40a1c45 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/helpers_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2019 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 handlers + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +func TestLazyTruncatedUserAgent(t *testing.T) { + req := &http.Request{} + req.Header = http.Header{} + + ua := "short-agent" + req.Header.Set("User-Agent", ua) + uaNotTruncated := &lazyTruncatedUserAgent{req} + assert.Equal(t, ua, fmt.Sprintf("%v", uaNotTruncated)) + + ua = "" + for i := 0; i < maxUserAgentLength*2; i++ { + ua = ua + "a" + } + req.Header.Set("User-Agent", ua) + uaTruncated := &lazyTruncatedUserAgent{req} + assert.NotEqual(t, ua, fmt.Sprintf("%v", uaTruncated)) + + usUnknown := &lazyTruncatedUserAgent{} + assert.Equal(t, "unknown", fmt.Sprintf("%v", usUnknown)) +} + +func TestLazyClientIP(t *testing.T) { + req := &http.Request{} + req.Header = http.Header{} + + ip := "127.0.0.1" + req.Header.Set("X-Forwarded-For", ip) + + clientIPWithReq := &lazyClientIP{req} + assert.Equal(t, ip, fmt.Sprintf("%v", clientIPWithReq)) + + clientIPWithoutReq := &lazyClientIP{} + assert.Equal(t, "unknown", fmt.Sprintf("%v", clientIPWithoutReq)) +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go index 8d589cc9113..301af03ee2c 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go @@ -59,7 +59,7 @@ const ( func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { // For performance tracking purposes. - trace := utiltrace.New("Patch", utiltrace.Field{"url", req.URL.Path}) + trace := utiltrace.New("Patch", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}}) defer trace.LogIfLong(500 * time.Millisecond) if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go index f1981df67ff..686df45b78d 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/update.go @@ -45,7 +45,7 @@ import ( func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interface) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { // For performance tracking purposes. - trace := utiltrace.New("Update", utiltrace.Field{"url", req.URL.Path}) + trace := utiltrace.New("Update", utiltrace.Field{Key: "url", Value: req.URL.Path}, utiltrace.Field{Key: "user-agent", Value: &lazyTruncatedUserAgent{req}}, utiltrace.Field{Key: "client", Value: &lazyClientIP{req}}) defer trace.LogIfLong(500 * time.Millisecond) if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {