Merge pull request #75699 from smarterclayton/fast_encode

Avoid allocations when building SelfLinks and fast path escape
k3s-v1.15.3
Kubernetes Prow Robot 2019-03-26 15:10:29 -07:00 committed by GitHub
commit 1514bb2141
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 116 additions and 8 deletions

View File

@ -34,6 +34,7 @@ go_test(
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library", "//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/google/gofuzz:go_default_library",
"//vendor/k8s.io/utils/trace:go_default_library", "//vendor/k8s.io/utils/trace:go_default_library",
], ],
) )

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -86,6 +87,21 @@ func (n ContextBasedNaming) Name(req *http.Request) (namespace, name string, err
return ns, requestInfo.Name, nil return ns, requestInfo.Name, nil
} }
// fastURLPathEncode encodes the provided path as a URL path
func fastURLPathEncode(path string) string {
for _, r := range []byte(path) {
switch {
case r >= '-' && r <= '9', r >= 'A' && r <= 'Z', r >= 'a' && r <= 'z':
// characters within this range do not require escaping
default:
var u url.URL
u.Path = path
return u.EscapedPath()
}
}
return path
}
func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error) { func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error) {
namespace, name, err := n.ObjectName(obj) namespace, name, err := n.ObjectName(obj)
if err == errEmptyName && len(requestInfo.Name) > 0 { if err == errEmptyName && len(requestInfo.Name) > 0 {
@ -101,19 +117,23 @@ func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj r
return n.SelfLinkPathPrefix + url.QueryEscape(name) + n.SelfLinkPathSuffix, nil return n.SelfLinkPathPrefix + url.QueryEscape(name) + n.SelfLinkPathSuffix, nil
} }
return n.SelfLinkPathPrefix + builder := strings.Builder{}
url.QueryEscape(namespace) + builder.Grow(len(n.SelfLinkPathPrefix) + len(namespace) + len(requestInfo.Resource) + len(name) + len(n.SelfLinkPathSuffix) + 8)
"/" + url.QueryEscape(requestInfo.Resource) + "/" + builder.WriteString(n.SelfLinkPathPrefix)
url.QueryEscape(name) + builder.WriteString(namespace)
n.SelfLinkPathSuffix, builder.WriteByte('/')
nil builder.WriteString(requestInfo.Resource)
builder.WriteByte('/')
builder.WriteString(name)
builder.WriteString(n.SelfLinkPathSuffix)
return fastURLPathEncode(builder.String()), nil
} }
func (n ContextBasedNaming) GenerateListLink(req *http.Request) (uri string, err error) { func (n ContextBasedNaming) GenerateListLink(req *http.Request) (uri string, err error) {
if len(req.URL.RawPath) > 0 { if len(req.URL.RawPath) > 0 {
return req.URL.RawPath, nil return req.URL.RawPath, nil
} }
return req.URL.EscapedPath(), nil return fastURLPathEncode(req.URL.Path), nil
} }
func (n ContextBasedNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) { func (n ContextBasedNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {

View File

@ -17,9 +17,13 @@ limitations under the License.
package handlers package handlers
import ( import (
"math/rand"
"net/url"
"testing" "testing"
"k8s.io/api/core/v1" fuzz "github.com/google/gofuzz"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -114,3 +118,62 @@ func TestGenerateLink(t *testing.T) {
} }
} }
} }
func Test_fastURLPathEncode_fuzz(t *testing.T) {
specialCases := []string{"/", "//", ".", "*", "/abc%"}
for _, test := range specialCases {
got := fastURLPathEncode(test)
u := url.URL{Path: test}
expected := u.EscapedPath()
if got != expected {
t.Errorf("%q did not match %q", got, expected)
}
}
f := fuzz.New().Funcs(
func(s *string, c fuzz.Continue) {
*s = randString(c.Rand)
},
)
for i := 0; i < 2000; i++ {
var test string
f.Fuzz(&test)
got := fastURLPathEncode(test)
u := url.URL{Path: test}
expected := u.EscapedPath()
if got != expected {
t.Errorf("%q did not match %q", got, expected)
}
}
}
// Unicode range fuzzer from github.com/google/gofuzz/fuzz.go
type charRange struct {
first, last rune
}
var unicodeRanges = []charRange{
{0x00, 0x255},
{' ', '~'}, // ASCII characters
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
}
// randString makes a random string up to 20 characters long. The returned string
// may include a variety of (valid) UTF-8 encodings.
func randString(r *rand.Rand) string {
n := r.Intn(20)
runes := make([]rune, n)
for i := range runes {
runes[i] = unicodeRanges[r.Intn(len(unicodeRanges))].choose(r)
}
return string(runes)
}
// choose returns a random unicode character from the given range, using the
// given randomness source.
func (r *charRange) choose(rand *rand.Rand) rune {
count := int64(r.last - r.first)
return r.first + rune(rand.Int63n(count))
}

View File

@ -757,6 +757,30 @@ func TestWatchHTTPTimeout(t *testing.T) {
func BenchmarkWatchHTTP(b *testing.B) { func BenchmarkWatchHTTP(b *testing.B) {
items := benchmarkItems(b) items := benchmarkItems(b)
// use ASCII names to capture the cost of handling ASCII only self-links
for i := range items {
item := &items[i]
item.Namespace = fmt.Sprintf("namespace-%d", i)
item.Name = fmt.Sprintf("reasonable-name-%d", i)
}
runWatchHTTPBenchmark(b, items)
}
func BenchmarkWatchHTTP_UTF8(b *testing.B) {
items := benchmarkItems(b)
// use UTF names to capture the cost of handling UTF-8 escaping in self-links
for i := range items {
item := &items[i]
item.Namespace = fmt.Sprintf("躀痢疈蜧í柢-%d", i)
item.Name = fmt.Sprintf("翏Ŏ熡韐-%d", i)
}
runWatchHTTPBenchmark(b, items)
}
func runWatchHTTPBenchmark(b *testing.B, items []example.Pod) {
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
handler := handle(map[string]rest.Storage{"simples": simpleStorage}) handler := handle(map[string]rest.Storage{"simples": simpleStorage})
server := httptest.NewServer(handler) server := httptest.NewServer(handler)