Store RequestInfo in Context

... in order to replace the manual RequestInfoResolver dependency injection
through out the code.
pull/6/head
Dr. Stefan Schimanski 2016-09-26 17:18:19 +02:00
parent 53631cf4f8
commit 10cbaf7ce0
20 changed files with 310 additions and 123 deletions

View File

@ -40,7 +40,6 @@ import (
type APIInstaller struct { type APIInstaller struct {
group *APIGroupVersion group *APIGroupVersion
info *RequestInfoResolver
prefix string // Path prefix where API resources are to be registered. prefix string // Path prefix where API resources are to be registered.
minRequestTimeout time.Duration minRequestTimeout time.Duration
} }
@ -70,8 +69,7 @@ func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []unversion
prefix: a.prefix + "/proxy/", prefix: a.prefix + "/proxy/",
storage: a.group.Storage, storage: a.group.Storage,
serializer: a.group.Serializer, serializer: a.group.Serializer,
context: a.group.Context, mapper: a.group.Context,
requestInfoResolver: a.info,
}) })
// Register the paths in a deterministic (sorted) order to get a deterministic swagger spec. // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.

View File

@ -67,10 +67,6 @@ type APIGroupVersion struct {
// GroupVersion is the external group version // GroupVersion is the external group version
GroupVersion unversioned.GroupVersion GroupVersion unversioned.GroupVersion
// RequestInfoResolver is used to parse URLs for the legacy proxy handler. Don't use this for anything else
// TODO: refactor proxy handler to use sub resources
RequestInfoResolver *RequestInfoResolver
// OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver // OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may // schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
@ -175,7 +171,6 @@ func (g *APIGroupVersion) newInstaller() *APIInstaller {
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version) prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
installer := &APIInstaller{ installer := &APIInstaller{
group: g, group: g,
info: g.RequestInfoResolver,
prefix: prefix, prefix: prefix,
minRequestTimeout: g.MinRequestTimeout, minRequestTimeout: g.MinRequestTimeout,
} }

View File

@ -39,11 +39,14 @@ import (
"k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apiserver/filters"
"k8s.io/kubernetes/pkg/apiserver/request"
apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing" apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/diff" "k8s.io/kubernetes/pkg/util/diff"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch"
"k8s.io/kubernetes/pkg/watch/versioned" "k8s.io/kubernetes/pkg/watch/versioned"
"k8s.io/kubernetes/plugin/pkg/admission/admit" "k8s.io/kubernetes/plugin/pkg/admission/admit"
@ -259,8 +262,6 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
template := APIGroupVersion{ template := APIGroupVersion{
Storage: storage, Storage: storage,
RequestInfoResolver: newTestRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
Copier: api.Scheme, Copier: api.Scheme,
@ -2388,7 +2389,6 @@ func TestUpdateREST(t *testing.T) {
return &APIGroupVersion{ return &APIGroupVersion{
Storage: storage, Storage: storage,
Root: "/" + prefix, Root: "/" + prefix,
RequestInfoResolver: newTestRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
Copier: api.Scheme, Copier: api.Scheme,
@ -2473,7 +2473,6 @@ func TestParentResourceIsRequired(t *testing.T) {
"simple/sub": storage, "simple/sub": storage,
}, },
Root: "/" + prefix, Root: "/" + prefix,
RequestInfoResolver: newTestRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
Copier: api.Scheme, Copier: api.Scheme,
@ -2505,7 +2504,6 @@ func TestParentResourceIsRequired(t *testing.T) {
"simple/sub": storage, "simple/sub": storage,
}, },
Root: "/" + prefix, Root: "/" + prefix,
RequestInfoResolver: newTestRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
Copier: api.Scheme, Copier: api.Scheme,
@ -3131,8 +3129,6 @@ func TestXGSubresource(t *testing.T) {
group := APIGroupVersion{ group := APIGroupVersion{
Storage: storage, Storage: storage,
RequestInfoResolver: newTestRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
Copier: api.Scheme, Copier: api.Scheme,
@ -3159,8 +3155,7 @@ func TestXGSubresource(t *testing.T) {
panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
} }
handler := defaultAPIServer{mux, container} server := newTestServer(defaultAPIServer{mux, container})
server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/subsimple") resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/subsimple")
@ -3249,3 +3244,16 @@ func BenchmarkUpdateProtobuf(b *testing.B) {
} }
b.StopTimer() b.StopTimer()
} }
func newTestServer(handler http.Handler) *httptest.Server {
handler = filters.WithRequestInfo(handler, newTestRequestInfoResolver(), requestContextMapper)
handler = api.WithRequestContext(handler, requestContextMapper)
return httptest.NewServer(handler)
}
func newTestRequestInfoResolver() *request.RequestInfoResolver {
return &request.RequestInfoResolver{
APIPrefixes: sets.NewString("api", "apis"),
GrouplessAPIPrefixes: sets.NewString("api"),
}
}

View File

@ -76,6 +76,13 @@ func notFound(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Not Found: %#v", req.RequestURI) fmt.Fprintf(w, "Not Found: %#v", req.RequestURI)
} }
// internalError renders a simple internal error
func internalError(w http.ResponseWriter, req *http.Request, err error) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Internal Server Error: %#v", req.RequestURI)
runtime.HandleError(err)
}
// errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under // errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under
// any known API prefixes // any known API prefixes
type errAPIPrefixNotFound struct { type errAPIPrefixNotFound struct {

View File

@ -89,7 +89,11 @@ func WithAudit(handler http.Handler, attributeGetter RequestAttributeGetter, out
return handler return handler
} }
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
attribs := attributeGetter.GetAttribs(req) attribs, err := attributeGetter.GetAttribs(req)
if err != nil {
internalError(w, req, err)
return
}
asuser := req.Header.Get("Impersonate-User") asuser := req.Header.Get("Impersonate-User")
if len(asuser) == 0 { if len(asuser) == 0 {
asuser = "<self>" asuser = "<self>"

View File

@ -29,9 +29,8 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver/request"
"k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/util/sets"
) )
type simpleResponseWriter struct { type simpleResponseWriter struct {
@ -72,22 +71,14 @@ func (*fakeHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(200) w.WriteHeader(200)
} }
type fakeRequestContextMapper struct{}
func (*fakeRequestContextMapper) Get(req *http.Request) (api.Context, bool) {
return api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "admin"}), true
}
func (*fakeRequestContextMapper) Update(req *http.Request, context api.Context) error {
return nil
}
func TestAudit(t *testing.T) { func TestAudit(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
attributeGetter := NewRequestAttributeGetter(&fakeRequestContextMapper{},
&apiserver.RequestInfoResolver{APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api")}) attributeGetter := NewRequestAttributeGetter(&fakeRequestContextMapper{
user: &user.DefaultInfo{Name: "admin"},
})
handler := WithAudit(&fakeHTTPHandler{}, attributeGetter, &buf) handler := WithAudit(&fakeHTTPHandler{}, attributeGetter, &buf)
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil) req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
req.RemoteAddr = "127.0.0.1" req.RemoteAddr = "127.0.0.1"
handler.ServeHTTP(httptest.NewRecorder(), req) handler.ServeHTTP(httptest.NewRecorder(), req)
@ -110,3 +101,26 @@ func TestAudit(t *testing.T) {
t.Errorf("Unexpected second line of audit: %s", line[1]) t.Errorf("Unexpected second line of audit: %s", line[1])
} }
} }
type fakeRequestContextMapper struct {
user *user.DefaultInfo
}
func (m *fakeRequestContextMapper) Get(req *http.Request) (api.Context, bool) {
ctx := api.NewContext()
if m.user != nil {
ctx = api.WithUser(ctx, m.user)
}
resolver := newTestRequestInfoResolver()
info, err := resolver.GetRequestInfo(req)
if err == nil {
ctx = request.WithRequestInfo(ctx, info)
}
return ctx, true
}
func (*fakeRequestContextMapper) Update(req *http.Request, context api.Context) error {
return nil
}

View File

@ -17,12 +17,13 @@ limitations under the License.
package filters package filters
import ( import (
"errors"
"net/http" "net/http"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver/request"
"k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/auth/authorizer"
) )
@ -33,7 +34,12 @@ func WithAuthorization(handler http.Handler, getAttribs RequestAttributeGetter,
return handler return handler
} }
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authorized, reason, err := a.Authorize(getAttribs.GetAttribs(req)) attrs, err := getAttribs.GetAttribs(req)
if err != nil {
internalError(w, req, err)
return
}
authorized, reason, err := a.Authorize(attrs)
if err != nil { if err != nil {
internalError(w, req, err) internalError(w, req, err)
return return
@ -49,31 +55,35 @@ func WithAuthorization(handler http.Handler, getAttribs RequestAttributeGetter,
// RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request // RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request
type RequestAttributeGetter interface { type RequestAttributeGetter interface {
GetAttribs(req *http.Request) (attribs authorizer.Attributes) GetAttribs(req *http.Request) (authorizer.Attributes, error)
} }
type requestAttributeGetter struct { type requestAttributeGetter struct {
requestContextMapper api.RequestContextMapper requestContextMapper api.RequestContextMapper
requestInfoResolver *apiserver.RequestInfoResolver
} }
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface. // NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, requestInfoResolver *apiserver.RequestInfoResolver) RequestAttributeGetter { func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper) RequestAttributeGetter {
return &requestAttributeGetter{requestContextMapper, requestInfoResolver} return &requestAttributeGetter{requestContextMapper}
} }
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes { func (r *requestAttributeGetter) GetAttribs(req *http.Request) (authorizer.Attributes, error) {
attribs := authorizer.AttributesRecord{} attribs := authorizer.AttributesRecord{}
ctx, ok := r.requestContextMapper.Get(req) ctx, ok := r.requestContextMapper.Get(req)
if ok { if !ok {
return nil, errors.New("no context found for request")
}
user, ok := api.UserFrom(ctx) user, ok := api.UserFrom(ctx)
if ok { if ok {
attribs.User = user attribs.User = user
} }
}
requestInfo, _ := r.requestInfoResolver.GetRequestInfo(req) requestInfo, found := request.RequestInfoFrom(ctx)
if !found {
return nil, errors.New("no RequestInfo found in the context")
}
// Start with common attributes that apply to resource and non-resource requests // Start with common attributes that apply to resource and non-resource requests
attribs.ResourceRequest = requestInfo.IsResourceRequest attribs.ResourceRequest = requestInfo.IsResourceRequest
@ -87,5 +97,5 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
attribs.Namespace = requestInfo.Namespace attribs.Namespace = requestInfo.Namespace
attribs.Name = requestInfo.Name attribs.Name = requestInfo.Name
return &attribs return &attribs, nil
} }

View File

@ -18,18 +18,18 @@ package filters
import ( import (
"net/http" "net/http"
"net/http/httptest"
"reflect" "reflect"
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/util/sets"
) )
func TestGetAttribs(t *testing.T) { func TestGetAttribs(t *testing.T) {
r := &requestAttributeGetter{api.NewRequestContextMapper(), &apiserver.RequestInfoResolver{APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api")}} mapper := api.NewRequestContextMapper()
attributeGetter := NewRequestAttributeGetter(mapper)
testcases := map[string]struct { testcases := map[string]struct {
Verb string Verb string
@ -103,8 +103,20 @@ func TestGetAttribs(t *testing.T) {
for k, tc := range testcases { for k, tc := range testcases {
req, _ := http.NewRequest(tc.Verb, tc.Path, nil) req, _ := http.NewRequest(tc.Verb, tc.Path, nil)
attribs := r.GetAttribs(req) req.RemoteAddr = "127.0.0.1"
if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
var attribs authorizer.Attributes
var err error
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
attribs, err = attributeGetter.GetAttribs(req)
})
handler = WithRequestInfo(handler, newTestRequestInfoResolver(), mapper)
handler = api.WithRequestContext(handler, mapper)
handler.ServeHTTP(httptest.NewRecorder(), req)
if err != nil {
t.Errorf("%s: unexpected error: %v", k, err)
} else if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs) t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs)
} }
} }

View File

@ -0,0 +1,46 @@
/*
Copyright 2016 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 filters
import (
"errors"
"fmt"
"net/http"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apiserver/request"
)
// WithRequestInfo attaches a RequestInfo to the context.
func WithRequestInfo(handler http.Handler, resolver *request.RequestInfoResolver, requestContextMapper api.RequestContextMapper) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx, ok := requestContextMapper.Get(req)
if !ok {
internalError(w, req, errors.New("no context found for request"))
return
}
info, err := resolver.GetRequestInfo(req)
if err != nil {
internalError(w, req, fmt.Errorf("failed to create RequestInfo: %v", err))
}
requestContextMapper.Update(req, request.WithRequestInfo(ctx, info))
handler.ServeHTTP(w, req)
})
}

View File

@ -0,0 +1,29 @@
/*
Copyright 2016 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 filters
import (
"k8s.io/kubernetes/pkg/apiserver/request"
"k8s.io/kubernetes/pkg/util/sets"
)
func newTestRequestInfoResolver() *request.RequestInfoResolver {
return &request.RequestInfoResolver{
APIPrefixes: sets.NewString("api", "apis"),
GrouplessAPIPrefixes: sets.NewString("api"),
}
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package apiserver package apiserver
import ( import (
"errors"
"io" "io"
"math/rand" "math/rand"
"net/http" "net/http"
@ -27,7 +28,7 @@ import (
"time" "time"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" apierrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apiserver/metrics" "k8s.io/kubernetes/pkg/apiserver/metrics"
@ -38,6 +39,7 @@ import (
proxyutil "k8s.io/kubernetes/pkg/util/proxy" proxyutil "k8s.io/kubernetes/pkg/util/proxy"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/apiserver/request"
) )
// ProxyHandler provides a http.Handler which will proxy traffic to locations // ProxyHandler provides a http.Handler which will proxy traffic to locations
@ -46,8 +48,7 @@ type ProxyHandler struct {
prefix string prefix string
storage map[string]rest.Storage storage map[string]rest.Storage
serializer runtime.NegotiatedSerializer serializer runtime.NegotiatedSerializer
context api.RequestContextMapper mapper api.RequestContextMapper
requestInfoResolver *RequestInfoResolver
} }
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@ -59,8 +60,19 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
reqStart := time.Now() reqStart := time.Now()
defer metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), w.Header().Get("Content-Type"), httpCode, reqStart) defer metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), w.Header().Get("Content-Type"), httpCode, reqStart)
requestInfo, err := r.requestInfoResolver.GetRequestInfo(req) ctx, ok := r.mapper.Get(req)
if err != nil || !requestInfo.IsResourceRequest { if !ok {
internalError(w, req, errors.New("Error getting request context"))
return
}
requestInfo, ok := request.RequestInfoFrom(ctx)
if !ok {
internalError(w, req, errors.New("Error getting RequestInfo from context"))
httpCode = http.StatusInternalServerError
return
}
if !requestInfo.IsResourceRequest {
notFound(w, req) notFound(w, req)
httpCode = http.StatusNotFound httpCode = http.StatusNotFound
return return
@ -68,10 +80,6 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
verb = requestInfo.Verb verb = requestInfo.Verb
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
ctx, ok := r.context.Get(req)
if !ok {
ctx = api.NewContext()
}
ctx = api.WithNamespace(ctx, namespace) ctx = api.WithNamespace(ctx, namespace)
if len(parts) < 2 { if len(parts) < 2 {
notFound(w, req) notFound(w, req)
@ -104,7 +112,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
redirector, ok := storage.(rest.Redirector) redirector, ok := storage.(rest.Redirector)
if !ok { if !ok {
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource) httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
httpCode = errorNegotiated(errors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.serializer, gv, w, req) httpCode = errorNegotiated(apierrors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.serializer, gv, w, req)
return return
} }

View File

@ -204,7 +204,7 @@ func TestProxyRequestContentLengthAndTransferEncoding(t *testing.T) {
expectedResourceNamespace: "default", expectedResourceNamespace: "default",
} }
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
server := httptest.NewServer(namespaceHandler) server := newTestServer(namespaceHandler)
defer server.Close() defer server.Close()
// Dial the proxy server // Dial the proxy server
@ -310,7 +310,7 @@ func TestProxy(t *testing.T) {
} }
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
namespaceServer := httptest.NewServer(namespaceHandler) namespaceServer := newTestServer(namespaceHandler)
defer namespaceServer.Close() defer namespaceServer.Close()
// test each supported URL pattern for finding the redirection resource in the proxy in a particular namespace // test each supported URL pattern for finding the redirection resource in the proxy in a particular namespace
@ -432,7 +432,7 @@ func TestProxyUpgrade(t *testing.T) {
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
server := httptest.NewServer(namespaceHandler) server := newTestServer(namespaceHandler)
defer server.Close() defer server.Close()
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/"+prefix+"/"+newGroupVersion.Group+"/"+newGroupVersion.Version+"/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/") ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/"+prefix+"/"+newGroupVersion.Group+"/"+newGroupVersion.Version+"/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
@ -496,7 +496,7 @@ func TestRedirectOnMissingTrailingSlash(t *testing.T) {
} }
handler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) handler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
server := httptest.NewServer(handler) server := newTestServer(handler)
defer server.Close() defer server.Close()
proxyTestPattern := "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/proxy/namespaces/ns/foo/id" + item.path proxyTestPattern := "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/proxy/namespaces/ns/foo/id" + item.path

View File

@ -0,0 +1,20 @@
/*
Copyright 2016 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 request contains everything around extracting info from
// a http request object.
// TODO: this package is temporary. Handlers must move into pkg/apiserver/handlers to avoid dependency cycle
package request // import "k8s.io/kubernetes/pkg/apiserver/request"

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2015 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package apiserver package request
import ( import (
"fmt" "fmt"
@ -68,8 +68,8 @@ var namespaceSubresources = sets.NewString("status", "finalize")
var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...) var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
type RequestInfoResolver struct { type RequestInfoResolver struct {
APIPrefixes sets.String APIPrefixes sets.String // without leading and trailing slashes
GrouplessAPIPrefixes sets.String GrouplessAPIPrefixes sets.String // without leading and trailing slashes
} }
// TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses // TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses
@ -103,7 +103,7 @@ type RequestInfoResolver struct {
// /api // /api
// /healthz // /healthz
// / // /
func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, error) { func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (*RequestInfo, error) {
// start with a non-resource request until proven otherwise // start with a non-resource request until proven otherwise
requestInfo := RequestInfo{ requestInfo := RequestInfo{
IsResourceRequest: false, IsResourceRequest: false,
@ -114,12 +114,12 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
currentParts := splitPath(req.URL.Path) currentParts := splitPath(req.URL.Path)
if len(currentParts) < 3 { if len(currentParts) < 3 {
// return a non-resource request // return a non-resource request
return requestInfo, nil return &requestInfo, nil
} }
if !r.APIPrefixes.Has(currentParts[0]) { if !r.APIPrefixes.Has(currentParts[0]) {
// return a non-resource request // return a non-resource request
return requestInfo, nil return &requestInfo, nil
} }
requestInfo.APIPrefix = currentParts[0] requestInfo.APIPrefix = currentParts[0]
currentParts = currentParts[1:] currentParts = currentParts[1:]
@ -128,7 +128,7 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?" // one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
if len(currentParts) < 3 { if len(currentParts) < 3 {
// return a non-resource request // return a non-resource request
return requestInfo, nil return &requestInfo, nil
} }
requestInfo.APIGroup = currentParts[0] requestInfo.APIGroup = currentParts[0]
@ -142,7 +142,7 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
// handle input of form /{specialVerb}/* // handle input of form /{specialVerb}/*
if specialVerbs.Has(currentParts[0]) { if specialVerbs.Has(currentParts[0]) {
if len(currentParts) < 2 { if len(currentParts) < 2 {
return requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL) return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL)
} }
requestInfo.Verb = currentParts[0] requestInfo.Verb = currentParts[0]
@ -204,7 +204,25 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
requestInfo.Verb = "deletecollection" requestInfo.Verb = "deletecollection"
} }
return requestInfo, nil return &requestInfo, nil
}
type requestInfoKeyType int
// requestInfoKey is the RequestInfo key for the context. It's of private type here. Because
// keys are interfaces and interfaces are equal when the type and the value is equal, this
// does not conflict with the keys defined in pkg/api.
const requestInfoKey requestInfoKeyType = iota
// WithRequestInfo returns a copy of parent in which the request info value is set
func WithRequestInfo(parent api.Context, info *RequestInfo) api.Context {
return api.WithValue(parent, requestInfoKey, info)
}
// RequestInfoFrom returns the value of the RequestInfo key on the ctx
func RequestInfoFrom(ctx api.Context) (*RequestInfo, bool) {
info, ok := ctx.Value(requestInfoKey).(*RequestInfo)
return info, ok
} }
// splitPath returns the segments for a URL path. // splitPath returns the segments for a URL path.

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2014 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package apiserver package request
import ( import (
"net/http" "net/http"
@ -194,5 +194,8 @@ func TestGetNonAPIRequestInfo(t *testing.T) {
} }
func newTestRequestInfoResolver() *RequestInfoResolver { func newTestRequestInfoResolver() *RequestInfoResolver {
return &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")} return &RequestInfoResolver{
APIPrefixes: sets.NewString("api", "apis"),
GrouplessAPIPrefixes: sets.NewString("api"),
}
} }

View File

@ -354,17 +354,21 @@ func (s *GenericAPIServer) buildHandlerChains(c *Config, handler http.Handler) (
// insecure filters // insecure filters
insecure = handler insecure = handler
insecure = genericfilters.WithPanicRecovery(insecure, s.NewRequestInfoResolver()) insecure = genericfilters.WithPanicRecovery(insecure, c.RequestContextMapper)
insecure = apiserverfilters.WithRequestInfo(insecure, s.NewRequestInfoResolver(), c.RequestContextMapper)
insecure = api.WithRequestContext(insecure, c.RequestContextMapper)
insecure = genericfilters.WithTimeoutForNonLongRunningRequests(insecure, c.LongRunningFunc) insecure = genericfilters.WithTimeoutForNonLongRunningRequests(insecure, c.LongRunningFunc)
// secure filters // secure filters
attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper, s.NewRequestInfoResolver()) attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper)
secure = handler secure = handler
secure = apiserverfilters.WithAuthorization(secure, attributeGetter, c.Authorizer) secure = apiserverfilters.WithAuthorization(secure, attributeGetter, c.Authorizer)
secure = apiserverfilters.WithImpersonation(secure, c.RequestContextMapper, c.Authorizer) secure = apiserverfilters.WithImpersonation(secure, c.RequestContextMapper, c.Authorizer)
secure = apiserverfilters.WithAudit(secure, attributeGetter, c.AuditWriter) // before impersonation to read original user secure = apiserverfilters.WithAudit(secure, attributeGetter, c.AuditWriter) // before impersonation to read original user
secure = authhandlers.WithAuthentication(secure, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth)) secure = authhandlers.WithAuthentication(secure, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth))
secure = genericfilters.WithPanicRecovery(secure, s.NewRequestInfoResolver()) secure = genericfilters.WithPanicRecovery(secure, c.RequestContextMapper)
secure = apiserverfilters.WithRequestInfo(secure, s.NewRequestInfoResolver(), c.RequestContextMapper)
secure = api.WithRequestContext(secure, c.RequestContextMapper)
secure = genericfilters.WithTimeoutForNonLongRunningRequests(secure, c.LongRunningFunc) secure = genericfilters.WithTimeoutForNonLongRunningRequests(secure, c.LongRunningFunc)
secure = genericfilters.WithMaxInFlightLimit(secure, c.MaxRequestsInFlight, c.LongRunningFunc) secure = genericfilters.WithMaxInFlightLimit(secure, c.MaxRequestsInFlight, c.LongRunningFunc)

View File

@ -22,14 +22,15 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apiserver" apierrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/apiserver/request"
"k8s.io/kubernetes/pkg/httplog" "k8s.io/kubernetes/pkg/httplog"
"k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/util/runtime"
) )
// WithPanicRecovery wraps an http Handler to recover and log panics. // WithPanicRecovery wraps an http Handler to recover and log panics.
func WithPanicRecovery(handler http.Handler, resolver *apiserver.RequestInfoResolver) http.Handler { func WithPanicRecovery(handler http.Handler, requestContextMapper api.RequestContextMapper) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer runtime.HandleCrash(func(err interface{}) { defer runtime.HandleCrash(func(err interface{}) {
http.Error(w, "This request caused apisever to panic. Look in log for details.", http.StatusInternalServerError) http.Error(w, "This request caused apisever to panic. Look in log for details.", http.StatusInternalServerError)
@ -37,8 +38,19 @@ func WithPanicRecovery(handler http.Handler, resolver *apiserver.RequestInfoReso
}) })
logger := httplog.NewLogged(req, &w) logger := httplog.NewLogged(req, &w)
requestInfo, err := resolver.GetRequestInfo(req)
if err != nil || requestInfo.Verb != "proxy" { var requestInfo *request.RequestInfo
ctx, ok := requestContextMapper.Get(req)
if !ok {
glog.Errorf("no context found for request, handler chain must be wrong")
} else {
requestInfo, ok = request.RequestInfoFrom(ctx)
if !ok {
glog.Errorf("no RequestInfo found in context, handler chain must be wrong")
}
}
if !ok || requestInfo.Verb != "proxy" {
logger.StacktraceWhen( logger.StacktraceWhen(
httplog.StatusIsNot( httplog.StatusIsNot(
http.StatusOK, http.StatusOK,
@ -52,12 +64,13 @@ func WithPanicRecovery(handler http.Handler, resolver *apiserver.RequestInfoReso
http.StatusUnauthorized, http.StatusUnauthorized,
http.StatusForbidden, http.StatusForbidden,
http.StatusNotModified, http.StatusNotModified,
errors.StatusUnprocessableEntity, apierrors.StatusUnprocessableEntity,
http.StatusSwitchingProtocols, http.StatusSwitchingProtocols,
), ),
) )
} }
defer logger.Log() defer logger.Log()
// Dispatch to the internal handler // Dispatch to the internal handler
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
}) })

View File

@ -42,6 +42,7 @@ import (
"k8s.io/kubernetes/pkg/apimachinery" "k8s.io/kubernetes/pkg/apimachinery"
"k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/apiserver/request"
"k8s.io/kubernetes/pkg/genericapiserver/openapi" "k8s.io/kubernetes/pkg/genericapiserver/openapi"
"k8s.io/kubernetes/pkg/genericapiserver/openapi/common" "k8s.io/kubernetes/pkg/genericapiserver/openapi/common"
"k8s.io/kubernetes/pkg/genericapiserver/options" "k8s.io/kubernetes/pkg/genericapiserver/options"
@ -187,8 +188,8 @@ func (s *GenericAPIServer) MinRequestTimeout() time.Duration {
return s.minRequestTimeout return s.minRequestTimeout
} }
func (s *GenericAPIServer) NewRequestInfoResolver() *apiserver.RequestInfoResolver { func (s *GenericAPIServer) NewRequestInfoResolver() *request.RequestInfoResolver {
return &apiserver.RequestInfoResolver{ return &request.RequestInfoResolver{
APIPrefixes: sets.NewString(strings.Trim(s.legacyAPIPrefix, "/"), strings.Trim(s.apiPrefix, "/")), // all possible API prefixes APIPrefixes: sets.NewString(strings.Trim(s.legacyAPIPrefix, "/"), strings.Trim(s.apiPrefix, "/")), // all possible API prefixes
GrouplessAPIPrefixes: sets.NewString(strings.Trim(s.legacyAPIPrefix, "/")), // APIPrefixes that won't have groups (legacy) GrouplessAPIPrefixes: sets.NewString(strings.Trim(s.legacyAPIPrefix, "/")), // APIPrefixes that won't have groups (legacy)
} }
@ -452,8 +453,6 @@ func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupV
func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion unversioned.GroupVersion) (*apiserver.APIGroupVersion, error) { func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion unversioned.GroupVersion) (*apiserver.APIGroupVersion, error) {
return &apiserver.APIGroupVersion{ return &apiserver.APIGroupVersion{
RequestInfoResolver: s.NewRequestInfoResolver(),
GroupVersion: groupVersion, GroupVersion: groupVersion,
ParameterCodec: apiGroupInfo.ParameterCodec, ParameterCodec: apiGroupInfo.ParameterCodec,

View File

@ -787,7 +787,6 @@ func (m *Master) thirdpartyapi(group, kind, version, pluralResource string) *api
return &apiserver.APIGroupVersion{ return &apiserver.APIGroupVersion{
Root: apiRoot, Root: apiRoot,
GroupVersion: externalVersion, GroupVersion: externalVersion,
RequestInfoResolver: m.NewRequestInfoResolver(),
Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme), Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme),
Convertor: api.Scheme, Convertor: api.Scheme,

View File

@ -47,7 +47,8 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver/request"
"k8s.io/kubernetes/pkg/generated/openapi"
"k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/genericapiserver"
"k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/kubelet/client"
"k8s.io/kubernetes/pkg/registry/core/endpoint" "k8s.io/kubernetes/pkg/registry/core/endpoint"
@ -72,7 +73,6 @@ import (
"github.com/go-openapi/validate" "github.com/go-openapi/validate"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/context" "golang.org/x/net/context"
"k8s.io/kubernetes/pkg/generated/openapi"
) )
// setUp is a convience function for setting up for (most) tests. // setUp is a convience function for setting up for (most) tests.
@ -192,7 +192,7 @@ func TestNamespaceSubresources(t *testing.T) {
master, etcdserver, _, _ := newMaster(t) master, etcdserver, _, _ := newMaster(t)
defer etcdserver.Terminate(t) defer etcdserver.Terminate(t)
expectedSubresources := apiserver.NamespaceSubResourcesForTest expectedSubresources := request.NamespaceSubResourcesForTest
foundSubresources := sets.NewString() foundSubresources := sets.NewString()
for k := range master.v1ResourcesStorage { for k := range master.v1ResourcesStorage {