Make genericapiserver handler chain customizable

This commit is contained in:
Dr. Stefan Schimanski 2016-09-28 11:26:50 +02:00 committed by deads2k
parent 7cfd0150e4
commit 68cee1d9ac
18 changed files with 269 additions and 171 deletions

View File

@ -231,8 +231,8 @@ func Run(s *options.ServerRunOptions) error {
return err return err
} }
routes.UIRedirect{}.Install(m.Mux, m.HandlerContainer) routes.UIRedirect{}.Install(m.HandlerContainer)
routes.Logs{}.Install(m.Mux, m.HandlerContainer) routes.Logs{}.Install(m.HandlerContainer)
restOptionsFactory := restOptionsFactory{ restOptionsFactory := restOptionsFactory{
storageFactory: storageFactory, storageFactory: storageFactory,

View File

@ -100,6 +100,7 @@ pkg/controller/volume/statusupdater
pkg/conversion/queryparams pkg/conversion/queryparams
pkg/credentialprovider/aws pkg/credentialprovider/aws
pkg/genericapiserver/filters pkg/genericapiserver/filters
pkg/genericapiserver/mux
pkg/genericapiserver/routes pkg/genericapiserver/routes
pkg/hyperkube pkg/hyperkube
pkg/kubelet/api pkg/kubelet/api

View File

@ -29,7 +29,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/emicklei/go-restful"
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
"github.com/golang/glog" "github.com/golang/glog"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
@ -46,6 +45,7 @@ import (
"k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider"
genericfilters "k8s.io/kubernetes/pkg/genericapiserver/filters" genericfilters "k8s.io/kubernetes/pkg/genericapiserver/filters"
"k8s.io/kubernetes/pkg/genericapiserver/mux"
"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"
"k8s.io/kubernetes/pkg/genericapiserver/routes" "k8s.io/kubernetes/pkg/genericapiserver/routes"
@ -94,9 +94,6 @@ type Config struct {
// Required, the interface for serializing and converting objects to and from the wire // Required, the interface for serializing and converting objects to and from the wire
Serializer runtime.NegotiatedSerializer Serializer runtime.NegotiatedSerializer
// If specified, all web services will be registered into this container
RestfulContainer *restful.Container
// If specified, requests will be allocated a random timeout between this value, and twice this value. // If specified, requests will be allocated a random timeout between this value, and twice this value.
// Note that it is up to the request handlers to ignore or honor this timeout. In seconds. // Note that it is up to the request handlers to ignore or honor this timeout. In seconds.
MinRequestTimeout int MinRequestTimeout int
@ -152,6 +149,8 @@ type Config struct {
// Port names should align with ports defined in ExtraServicePorts // Port names should align with ports defined in ExtraServicePorts
ExtraEndpointPorts []api.EndpointPort ExtraEndpointPorts []api.EndpointPort
// If non-zero, the "kubernetes" services uses this port as NodePort.
// TODO(sttts): move into master
KubernetesServiceNodePort int KubernetesServiceNodePort int
// EnableOpenAPISupport enables OpenAPI support. Allow downstream customers to disable OpenAPI spec. // EnableOpenAPISupport enables OpenAPI support. Allow downstream customers to disable OpenAPI spec.
@ -173,6 +172,9 @@ type Config struct {
// Predicate which is true for paths of long-running http requests // Predicate which is true for paths of long-running http requests
LongRunningFunc genericfilters.LongRunningRequestCheck LongRunningFunc genericfilters.LongRunningRequestCheck
// Build the handler chains by decorating the apiHandler.
BuildHandlerChainsFunc func(apiHandler http.Handler, c *Config) (secure, insecure http.Handler)
} }
type ServingInfo struct { type ServingInfo struct {
@ -323,6 +325,9 @@ func (c *Config) Complete() completedConfig {
} }
c.ExternalHost = hostAndPort c.ExternalHost = hostAndPort
} }
if c.BuildHandlerChainsFunc == nil {
c.BuildHandlerChainsFunc = DefaultBuildHandlerChain
}
return completedConfig{c} return completedConfig{c}
} }
@ -391,15 +396,7 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
openAPIDefinitions: c.OpenAPIDefinitions, openAPIDefinitions: c.OpenAPIDefinitions,
} }
if c.RestfulContainer != nil { s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer)
s.HandlerContainer = c.RestfulContainer
} else {
s.HandlerContainer = NewHandlerContainer(http.NewServeMux(), c.Serializer)
}
// Use CurlyRouter to be able to use regular expressions in paths. Regular expressions are required in paths for example for proxy (where the path is proxy/{kind}/{name}/{*})
s.HandlerContainer.Router(restful.CurlyRouter{})
s.Mux = apiserver.NewPathRecorderMux(s.HandlerContainer.ServeMux)
apiserver.InstallServiceErrorHandler(s.Serializer, s.HandlerContainer)
if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil { if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil {
s.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{ s.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{
@ -409,50 +406,50 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
} }
s.installAPI(c.Config) s.installAPI(c.Config)
s.Handler, s.InsecureHandler = s.buildHandlerChains(c.Config, http.Handler(s.Mux.BaseMux().(*http.ServeMux)))
s.Handler, s.InsecureHandler = c.BuildHandlerChainsFunc(s.HandlerContainer.ServeMux, c.Config)
return s, nil return s, nil
} }
func (s *GenericAPIServer) buildHandlerChains(c *Config, handler http.Handler) (secure http.Handler, insecure http.Handler) { func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
// filters which insecure and secure have in common
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
// insecure filters
insecure = handler
insecure = genericfilters.WithPanicRecovery(insecure, c.RequestContextMapper)
insecure = apiserverfilters.WithRequestInfo(insecure, NewRequestInfoResolver(c), c.RequestContextMapper)
insecure = api.WithRequestContext(insecure, c.RequestContextMapper)
insecure = genericfilters.WithTimeoutForNonLongRunningRequests(insecure, c.LongRunningFunc)
// secure filters
attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper) attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper)
secure = handler
secure = apiserverfilters.WithAuthorization(secure, attributeGetter, c.Authorizer)
secure = apiserverfilters.WithImpersonation(secure, c.RequestContextMapper, c.Authorizer)
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 = genericfilters.WithPanicRecovery(secure, c.RequestContextMapper)
secure = apiserverfilters.WithRequestInfo(secure, NewRequestInfoResolver(c), c.RequestContextMapper)
secure = api.WithRequestContext(secure, c.RequestContextMapper)
secure = genericfilters.WithTimeoutForNonLongRunningRequests(secure, c.LongRunningFunc)
secure = genericfilters.WithMaxInFlightLimit(secure, c.MaxRequestsInFlight, c.LongRunningFunc)
return generic := func(handler http.Handler) http.Handler {
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
handler = genericfilters.WithPanicRecovery(handler, c.RequestContextMapper)
handler = apiserverfilters.WithRequestInfo(handler, NewRequestInfoResolver(c), c.RequestContextMapper)
handler = api.WithRequestContext(handler, c.RequestContextMapper)
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc)
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.LongRunningFunc)
return handler
}
audit := func(handler http.Handler) http.Handler {
return apiserverfilters.WithAudit(handler, attributeGetter, c.AuditWriter)
}
protect := func(handler http.Handler) http.Handler {
handler = apiserverfilters.WithAuthorization(handler, attributeGetter, c.Authorizer)
handler = apiserverfilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer)
handler = audit(handler) // before impersonation to read original user
handler = authhandlers.WithAuthentication(handler, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth))
return handler
}
return generic(protect(apiHandler)), generic(audit(apiHandler))
} }
func (s *GenericAPIServer) installAPI(c *Config) { func (s *GenericAPIServer) installAPI(c *Config) {
if c.EnableIndex { if c.EnableIndex {
routes.Index{}.Install(s.Mux, s.HandlerContainer) routes.Index{}.Install(s.HandlerContainer)
} }
if c.EnableSwaggerSupport && c.EnableSwaggerUI { if c.EnableSwaggerSupport && c.EnableSwaggerUI {
routes.SwaggerUI{}.Install(s.Mux, s.HandlerContainer) routes.SwaggerUI{}.Install(s.HandlerContainer)
} }
if c.EnableProfiling { if c.EnableProfiling {
routes.Profiling{}.Install(s.Mux, s.HandlerContainer) routes.Profiling{}.Install(s.HandlerContainer)
} }
if c.EnableVersion { if c.EnableVersion {
routes.Version{}.Install(s.Mux, s.HandlerContainer) routes.Version{}.Install(s.HandlerContainer)
} }
s.HandlerContainer.Add(s.DynamicApisDiscovery()) s.HandlerContainer.Add(s.DynamicApisDiscovery())
} }

View File

@ -42,6 +42,7 @@ import (
"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/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
genericmux "k8s.io/kubernetes/pkg/genericapiserver/mux"
"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/runtime" "k8s.io/kubernetes/pkg/runtime"
@ -118,9 +119,8 @@ type GenericAPIServer struct {
// requestContextMapper provides a way to get the context for a request. It may be nil. // requestContextMapper provides a way to get the context for a request. It may be nil.
requestContextMapper api.RequestContextMapper requestContextMapper api.RequestContextMapper
Mux *apiserver.PathRecorderMux // The registered APIs
HandlerContainer *restful.Container HandlerContainer *genericmux.APIContainer
MasterCount int
SecureServingInfo *ServingInfo SecureServingInfo *ServingInfo
InsecureServingInfo *ServingInfo InsecureServingInfo *ServingInfo
@ -128,13 +128,9 @@ type GenericAPIServer struct {
// ExternalAddress is the address (hostname or IP and port) that should be used in // ExternalAddress is the address (hostname or IP and port) that should be used in
// external (public internet) URLs for this GenericAPIServer. // external (public internet) URLs for this GenericAPIServer.
ExternalAddress string ExternalAddress string
// ClusterIP is the IP address of the GenericAPIServer within the cluster. // ClusterIP is the IP address of the GenericAPIServer within the cluster.
ClusterIP net.IP ClusterIP net.IP
PublicReadWritePort int
ServiceReadWriteIP net.IP
ServiceReadWritePort int
ExtraServicePorts []api.ServicePort
ExtraEndpointPorts []api.EndpointPort
// storage contains the RESTful endpoints exposed by this GenericAPIServer // storage contains the RESTful endpoints exposed by this GenericAPIServer
storage map[string]rest.Storage storage map[string]rest.Storage
@ -150,26 +146,31 @@ type GenericAPIServer struct {
// Used for custom proxy dialing, and proxy TLS options // Used for custom proxy dialing, and proxy TLS options
ProxyTransport http.RoundTripper ProxyTransport http.RoundTripper
KubernetesServiceNodePort int
// Map storing information about all groups to be exposed in discovery response. // Map storing information about all groups to be exposed in discovery response.
// The map is from name to the group. // The map is from name to the group.
apiGroupsForDiscoveryLock sync.RWMutex apiGroupsForDiscoveryLock sync.RWMutex
apiGroupsForDiscovery map[string]unversioned.APIGroup apiGroupsForDiscovery map[string]unversioned.APIGroup
// See Config.$name for documentation of these flags
enableOpenAPISupport bool
openAPIInfo spec.Info
openAPIDefaultResponse spec.Response
openAPIDefinitions *common.OpenAPIDefinitions
// PostStartHooks are each called after the server has started listening, in a separate go func for each // PostStartHooks are each called after the server has started listening, in a separate go func for each
// with no guaranteee of ordering between them. The map key is a name used for error reporting. // with no guaranteee of ordering between them. The map key is a name used for error reporting.
// It may kill the process with a panic if it wishes to by returning an error // It may kill the process with a panic if it wishes to by returning an error
postStartHooks map[string]PostStartHookFunc postStartHooks map[string]PostStartHookFunc
postStartHookLock sync.Mutex postStartHookLock sync.Mutex
postStartHooksCalled bool postStartHooksCalled bool
// See Config.$name for documentation of these flags:
enableOpenAPISupport bool
openAPIInfo spec.Info
openAPIDefaultResponse spec.Response
openAPIDefinitions *common.OpenAPIDefinitions
MasterCount int
KubernetesServiceNodePort int // TODO(sttts): move into master
PublicReadWritePort int
ServiceReadWriteIP net.IP
ServiceReadWritePort int
ExtraServicePorts []api.ServicePort
ExtraEndpointPorts []api.EndpointPort
} }
func init() { func init() {
@ -191,33 +192,6 @@ func (s *GenericAPIServer) MinRequestTimeout() time.Duration {
return s.minRequestTimeout return s.minRequestTimeout
} }
// HandleWithAuth adds an http.Handler for pattern to an http.ServeMux
// Applies the same authentication and authorization (if any is configured)
// to the request is used for the GenericAPIServer's built-in endpoints.
func (s *GenericAPIServer) HandleWithAuth(pattern string, handler http.Handler) {
// TODO: Add a way for plugged-in endpoints to translate their
// URLs into attributes that an Authorizer can understand, and have
// sensible policy defaults for plugged-in endpoints. This will be different
// for generic endpoints versus REST object endpoints.
// TODO: convert to go-restful
s.Mux.Handle(pattern, handler)
}
// HandleFuncWithAuth adds an http.Handler for pattern to an http.ServeMux
// Applies the same authentication and authorization (if any is configured)
// to the request is used for the GenericAPIServer's built-in endpoints.
func (s *GenericAPIServer) HandleFuncWithAuth(pattern string, handler func(http.ResponseWriter, *http.Request)) {
// TODO: convert to go-restful
s.Mux.HandleFunc(pattern, handler)
}
func NewHandlerContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *restful.Container {
container := restful.NewContainer()
container.ServeMux = mux
apiserver.InstallRecoverHandler(s, container)
return container
}
func (s *GenericAPIServer) Run() { func (s *GenericAPIServer) Run() {
// install APIs which depend on other APIs to be installed // install APIs which depend on other APIs to be installed
if s.enableSwaggerSupport { if s.enableSwaggerSupport {
@ -227,7 +201,7 @@ func (s *GenericAPIServer) Run() {
s.InstallOpenAPI() s.InstallOpenAPI()
} }
if s.SecureServingInfo != nil { if s.SecureServingInfo != nil && s.Handler != nil {
secureServer := &http.Server{ secureServer := &http.Server{
Addr: s.SecureServingInfo.BindAddress, Addr: s.SecureServingInfo.BindAddress,
Handler: s.Handler, Handler: s.Handler,
@ -282,7 +256,7 @@ func (s *GenericAPIServer) Run() {
}() }()
} }
if s.InsecureServingInfo != nil { if s.InsecureServingInfo != nil && s.InsecureHandler != nil {
insecureServer := &http.Server{ insecureServer := &http.Server{
Addr: s.InsecureServingInfo.BindAddress, Addr: s.InsecureServingInfo.BindAddress,
Handler: s.InsecureHandler, Handler: s.InsecureHandler,
@ -343,14 +317,14 @@ func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
} }
if err := apiGroupVersion.InstallREST(s.HandlerContainer); err != nil { if err := apiGroupVersion.InstallREST(s.HandlerContainer.Container); err != nil {
return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err) return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err)
} }
} }
// Install the version handler. // Install the version handler.
if apiGroupInfo.IsLegacyGroup { if apiGroupInfo.IsLegacyGroup {
// Add a handler at /api to enumerate the supported api versions. // Add a handler at /api to enumerate the supported api versions.
apiserver.AddApiWebService(s.Serializer, s.HandlerContainer, apiPrefix, func(req *restful.Request) *unversioned.APIVersions { apiserver.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *unversioned.APIVersions {
apiVersionsForDiscovery := unversioned.APIVersions{ apiVersionsForDiscovery := unversioned.APIVersions{
ServerAddressByClientCIDRs: s.getServerAddressByClientCIDRs(req.Request), ServerAddressByClientCIDRs: s.getServerAddressByClientCIDRs(req.Request),
Versions: apiVersions, Versions: apiVersions,
@ -487,7 +461,7 @@ func (s *GenericAPIServer) getSwaggerConfig() *swagger.Config {
// of swagger, so that other resource types show up in the documentation. // of swagger, so that other resource types show up in the documentation.
func (s *GenericAPIServer) InstallSwaggerAPI() { func (s *GenericAPIServer) InstallSwaggerAPI() {
// Enable swagger UI and discovery API // Enable swagger UI and discovery API
swagger.RegisterSwaggerService(*s.getSwaggerConfig(), s.HandlerContainer) swagger.RegisterSwaggerService(*s.getSwaggerConfig(), s.HandlerContainer.Container)
} }
// InstallOpenAPI installs spec endpoints for each web service. // InstallOpenAPI installs spec endpoints for each web service.
@ -508,7 +482,7 @@ func (s *GenericAPIServer) InstallOpenAPI() {
Info: &info, Info: &info,
DefaultResponse: &s.openAPIDefaultResponse, DefaultResponse: &s.openAPIDefaultResponse,
OpenAPIDefinitions: s.openAPIDefinitions, OpenAPIDefinitions: s.openAPIDefinitions,
}, s.HandlerContainer) }, s.HandlerContainer.Container)
if err != nil { if err != nil {
glog.Fatalf("Failed to register open api spec for %v: %v", w.RootPath(), err) glog.Fatalf("Failed to register open api spec for %v: %v", w.RootPath(), err)
} }
@ -521,7 +495,7 @@ func (s *GenericAPIServer) InstallOpenAPI() {
Info: &s.openAPIInfo, Info: &s.openAPIInfo,
DefaultResponse: &s.openAPIDefaultResponse, DefaultResponse: &s.openAPIDefaultResponse,
OpenAPIDefinitions: s.openAPIDefinitions, OpenAPIDefinitions: s.openAPIDefinitions,
}, s.HandlerContainer) }, s.HandlerContainer.Container)
if err != nil { if err != nil {
glog.Fatalf("Failed to register open api spec for root: %v", err) glog.Fatalf("Failed to register open api spec for root: %v", err)
} }

View File

@ -20,6 +20,7 @@ import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
@ -32,12 +33,11 @@ import (
"k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apimachinery/registered"
"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/auth/user" "k8s.io/kubernetes/pkg/auth/user"
genericmux "k8s.io/kubernetes/pkg/genericapiserver/mux"
ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
utilnet "k8s.io/kubernetes/pkg/util/net" utilnet "k8s.io/kubernetes/pkg/util/net"
@ -138,7 +138,7 @@ func TestInstallAPIGroups(t *testing.T) {
s.InstallAPIGroup(&apiGroupsInfo[i]) s.InstallAPIGroup(&apiGroupsInfo[i])
} }
server := httptest.NewServer(s.HandlerContainer.ServeMux) server := httptest.NewServer(s.InsecureHandler)
defer server.Close() defer server.Close()
validPaths := []string{ validPaths := []string{
// "/api" // "/api"
@ -158,41 +158,64 @@ func TestInstallAPIGroups(t *testing.T) {
} }
} }
// TestNewHandlerContainer verifies that NewHandlerContainer uses the // TestCustomHandlerChain verifies the handler chain with custom handler chain builder functions.
// mux provided func TestCustomHandlerChain(t *testing.T) {
func TestNewHandlerContainer(t *testing.T) { etcdserver, config, _ := setUp(t)
assert := assert.New(t)
mux := http.NewServeMux()
container := NewHandlerContainer(mux, nil)
assert.Equal(mux, container.ServeMux, "ServerMux's do not match")
}
// TestHandleWithAuth verifies HandleWithAuth adds the path
// to the MuxHelper.RegisteredPaths.
func TestHandleWithAuth(t *testing.T) {
etcdserver, _, assert := setUp(t)
defer etcdserver.Terminate(t) defer etcdserver.Terminate(t)
server := &GenericAPIServer{} var protected, called bool
server.Mux = apiserver.NewPathRecorderMux(http.NewServeMux())
handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) }
server.HandleWithAuth("/test", http.HandlerFunc(handler))
assert.Contains(server.Mux.HandledPaths(), "/test", "Path not found in MuxHelper") config.Serializer = api.Codecs
} config.BuildHandlerChainsFunc = func(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
protected = true
apiHandler.ServeHTTP(w, req)
}), http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
protected = false
apiHandler.ServeHTTP(w, req)
})
}
handler := http.HandlerFunc(func(r http.ResponseWriter, req *http.Request) {
called = true
})
// TestHandleFuncWithAuth verifies HandleFuncWithAuth adds the path s, err := config.Complete().New()
// to the MuxHelper.RegisteredPaths. if err != nil {
func TestHandleFuncWithAuth(t *testing.T) { t.Fatalf("Error in bringing up the server: %v", err)
etcdserver, _, assert := setUp(t) }
defer etcdserver.Terminate(t)
server := &GenericAPIServer{} s.HandlerContainer.NonSwaggerRoutes.Handle("/nonswagger", handler)
server.Mux = apiserver.NewPathRecorderMux(http.NewServeMux()) s.HandlerContainer.SecretRoutes.Handle("/secret", handler)
handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) }
server.HandleFuncWithAuth("/test", handler)
assert.Contains(server.Mux.HandledPaths(), "/test", "Path not found in MuxHelper") type Test struct {
handler http.Handler
path string
protected bool
}
for i, test := range []Test{
{s.Handler, "/nonswagger", true},
{s.Handler, "/secret", true},
{s.InsecureHandler, "/nonswagger", false},
{s.InsecureHandler, "/secret", false},
} {
protected, called = false, false
var w io.Reader
req, err := http.NewRequest("GET", test.path, w)
if err != nil {
t.Errorf("%d: Unexpected http error: %v", i, err)
continue
}
test.handler.ServeHTTP(httptest.NewRecorder(), req)
if !called {
t.Errorf("%d: Expected handler to be called.", i)
}
if test.protected != protected {
t.Errorf("%d: Expected protected=%v, got protected=%v.", i, test.protected, protected)
}
}
} }
// TestNotRestRoutesHaveAuth checks that special non-routes are behind authz/authn. // TestNotRestRoutesHaveAuth checks that special non-routes are behind authz/authn.
@ -267,7 +290,7 @@ func TestInstallSwaggerAPI(t *testing.T) {
mux := http.NewServeMux() mux := http.NewServeMux()
server := &GenericAPIServer{} server := &GenericAPIServer{}
server.HandlerContainer = NewHandlerContainer(mux, nil) server.HandlerContainer = genericmux.NewAPIContainer(mux, nil)
// Ensure swagger isn't installed without the call // Ensure swagger isn't installed without the call
ws := server.HandlerContainer.RegisteredWebServices() ws := server.HandlerContainer.RegisteredWebServices()
@ -286,7 +309,7 @@ func TestInstallSwaggerAPI(t *testing.T) {
// Empty externalHost verification // Empty externalHost verification
mux = http.NewServeMux() mux = http.NewServeMux()
server.HandlerContainer = NewHandlerContainer(mux, nil) server.HandlerContainer = genericmux.NewAPIContainer(mux, nil)
server.ExternalAddress = "" server.ExternalAddress = ""
server.ClusterIP = net.IPv4(10, 10, 10, 10) server.ClusterIP = net.IPv4(10, 10, 10, 10)
server.PublicReadWritePort = 1010 server.PublicReadWritePort = 1010
@ -328,7 +351,7 @@ func TestDiscoveryAtAPIS(t *testing.T) {
master, etcdserver, _, assert := newMaster(t) master, etcdserver, _, assert := newMaster(t)
defer etcdserver.Terminate(t) defer etcdserver.Terminate(t)
server := httptest.NewServer(master.HandlerContainer.ServeMux) server := httptest.NewServer(master.InsecureHandler)
groupList, err := getGroupList(server) groupList, err := getGroupList(server)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)

View File

@ -0,0 +1,52 @@
/*
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 mux
import (
"net/http"
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/runtime"
)
// APIContainer is a restful container which in addition support registering
// handlers that do not show up in swagger or in /
type APIContainer struct {
*restful.Container
NonSwaggerRoutes PathRecorderMux
SecretRoutes Mux
}
// NewAPIContainer constructs a new container for APIs
func NewAPIContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *APIContainer {
c := APIContainer{
Container: restful.NewContainer(),
NonSwaggerRoutes: PathRecorderMux{
mux: mux,
},
SecretRoutes: mux,
}
c.Container.ServeMux = mux
c.Container.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
apiserver.InstallRecoverHandler(s, c.Container)
apiserver.InstallServiceErrorHandler(s, c.Container)
return &c
}

View File

@ -0,0 +1,40 @@
/*
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 mux
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewAPIContainer(t *testing.T) {
mux := http.NewServeMux()
c := NewAPIContainer(mux, nil)
assert.Equal(t, mux, c.SecretRoutes.(*http.ServeMux), "SecretRoutes ServeMux's do not match")
assert.Equal(t, mux, c.Container.ServeMux, "Container ServeMux's do not match")
}
func TestSecretHandlers(t *testing.T) {
mux := http.NewServeMux()
c := NewAPIContainer(mux, nil)
c.SecretRoutes.HandleFunc("/secret", func(http.ResponseWriter, *http.Request) {})
c.NonSwaggerRoutes.HandleFunc("/nonswagger", func(http.ResponseWriter, *http.Request) {})
assert.NotContains(t, c.NonSwaggerRoutes.HandledPaths(), "/secret")
assert.Contains(t, c.NonSwaggerRoutes.HandledPaths(), "/nonswagger")
}

View File

@ -0,0 +1,18 @@
/*
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 mux contains abstractions for http multiplexing of APIs.
package mux

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 mux
import ( import (
"net/http" "net/http"

View File

@ -20,19 +20,17 @@ import (
"net/http" "net/http"
"sort" "sort"
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/genericapiserver/mux"
) )
// Index provides a webservice for the http root / listing all known paths. // Index provides a webservice for the http root / listing all known paths.
type Index struct{} type Index struct{}
// Install adds the Index webservice to the given mux. // Install adds the Index webservice to the given mux.
func (i Index) Install(mux *apiserver.PathRecorderMux, c *restful.Container) { func (i Index) Install(c *mux.APIContainer) {
// do not register this using restful Webservice since we do not want to surface this in api docs. c.SecretRoutes.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
mux.BaseMux().HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
status := http.StatusOK status := http.StatusOK
if r.URL.Path != "/" && r.URL.Path != "/index.html" { if r.URL.Path != "/" && r.URL.Path != "/index.html" {
// Since "/" matches all paths, handleIndex is called for all paths for which there is no handler registered. // Since "/" matches all paths, handleIndex is called for all paths for which there is no handler registered.
@ -45,7 +43,7 @@ func (i Index) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
handledPaths = append(handledPaths, ws.RootPath()) handledPaths = append(handledPaths, ws.RootPath())
} }
// Extract the paths handled using mux handler. // Extract the paths handled using mux handler.
handledPaths = append(handledPaths, mux.HandledPaths()...) handledPaths = append(handledPaths, c.NonSwaggerRoutes.HandledPaths()...)
sort.Strings(handledPaths) sort.Strings(handledPaths)
apiserver.WriteRawJSON(status, unversioned.RootPaths{Paths: handledPaths}, w) apiserver.WriteRawJSON(status, unversioned.RootPaths{Paths: handledPaths}, w)
}) })

View File

@ -17,18 +17,17 @@ limitations under the License.
package routes package routes
import ( import (
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver"
"net/http/pprof" "net/http/pprof"
"k8s.io/kubernetes/pkg/genericapiserver/mux"
) )
// Profiling adds handlers for pprof under /debug/pprof. // Profiling adds handlers for pprof under /debug/pprof.
type Profiling struct{} type Profiling struct{}
// Install adds the Profiling webservice to the given mux. // Install adds the Profiling webservice to the given mux.
func (d Profiling) Install(mux *apiserver.PathRecorderMux, c *restful.Container) { func (d Profiling) Install(c *mux.APIContainer) {
mux.BaseMux().HandleFunc("/debug/pprof/", pprof.Index) c.SecretRoutes.HandleFunc("/debug/pprof/", pprof.Index)
mux.BaseMux().HandleFunc("/debug/pprof/profile", pprof.Profile) c.SecretRoutes.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.BaseMux().HandleFunc("/debug/pprof/symbol", pprof.Symbol) c.SecretRoutes.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
} }

View File

@ -20,9 +20,8 @@ import (
"net/http" "net/http"
assetfs "github.com/elazarl/go-bindata-assetfs" assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/genericapiserver/mux"
"k8s.io/kubernetes/pkg/genericapiserver/routes/data/swagger" "k8s.io/kubernetes/pkg/genericapiserver/routes/data/swagger"
) )
@ -30,12 +29,12 @@ import (
type SwaggerUI struct{} type SwaggerUI struct{}
// Install adds the SwaggerUI webservice to the given mux. // Install adds the SwaggerUI webservice to the given mux.
func (l SwaggerUI) Install(mux *apiserver.PathRecorderMux, c *restful.Container) { func (l SwaggerUI) Install(c *mux.APIContainer) {
fileServer := http.FileServer(&assetfs.AssetFS{ fileServer := http.FileServer(&assetfs.AssetFS{
Asset: swagger.Asset, Asset: swagger.Asset,
AssetDir: swagger.AssetDir, AssetDir: swagger.AssetDir,
Prefix: "third_party/swagger-ui", Prefix: "third_party/swagger-ui",
}) })
prefix := "/swagger-ui/" prefix := "/swagger-ui/"
mux.Handle(prefix, http.StripPrefix(prefix, fileServer)) c.NonSwaggerRoutes.Handle(prefix, http.StripPrefix(prefix, fileServer))
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/genericapiserver/mux"
"k8s.io/kubernetes/pkg/version" "k8s.io/kubernetes/pkg/version"
) )
@ -29,7 +30,7 @@ import (
type Version struct{} type Version struct{}
// Install registers the APIServer's `/version` handler. // Install registers the APIServer's `/version` handler.
func (v Version) Install(mux *apiserver.PathRecorderMux, c *restful.Container) { func (v Version) Install(c *mux.APIContainer) {
// Set up a service to return the git code version. // Set up a service to return the git code version.
versionWS := new(restful.WebService) versionWS := new(restful.WebService)
versionWS.Path("/version") versionWS.Path("/version")

View File

@ -192,10 +192,10 @@ func (c completedConfig) New() (*Master, error) {
} }
if c.EnableUISupport { if c.EnableUISupport {
routes.UIRedirect{}.Install(s.Mux, s.HandlerContainer) routes.UIRedirect{}.Install(s.HandlerContainer)
} }
if c.EnableLogsSupport { if c.EnableLogsSupport {
routes.Logs{}.Install(s.Mux, s.HandlerContainer) routes.Logs{}.Install(s.HandlerContainer)
} }
m := &Master{ m := &Master{
@ -284,12 +284,12 @@ func (m *Master) InstallAPIs(c *Config) {
Help: "The time since the last successful synchronization of the SSH tunnels for proxy requests.", Help: "The time since the last successful synchronization of the SSH tunnels for proxy requests.",
}, func() float64 { return float64(m.tunneler.SecondsSinceSync()) }) }, func() float64 { return float64(m.tunneler.SecondsSinceSync()) })
} }
healthz.InstallHandler(m.Mux, healthzChecks...) healthz.InstallHandler(&m.HandlerContainer.NonSwaggerRoutes, healthzChecks...)
if c.GenericConfig.EnableProfiling { if c.GenericConfig.EnableProfiling {
routes.MetricsWithReset{}.Install(m.Mux, m.HandlerContainer) routes.MetricsWithReset{}.Install(m.HandlerContainer)
} else { } else {
routes.DefaultMetrics{}.Install(m.Mux, m.HandlerContainer) routes.DefaultMetrics{}.Install(m.HandlerContainer)
} }
// Install third party resource support if requested // Install third party resource support if requested
@ -612,10 +612,10 @@ func (m *Master) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource)
// the group with the new API // the group with the new API
if m.hasThirdPartyGroupStorage(path) { if m.hasThirdPartyGroupStorage(path) {
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup) m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup)
return thirdparty.UpdateREST(m.HandlerContainer) return thirdparty.UpdateREST(m.HandlerContainer.Container)
} }
if err := thirdparty.InstallREST(m.HandlerContainer); err != nil { if err := thirdparty.InstallREST(m.HandlerContainer.Container); err != nil {
glog.Errorf("Unable to setup thirdparty api: %v", err) glog.Errorf("Unable to setup thirdparty api: %v", err)
} }
m.HandlerContainer.Add(apiserver.NewGroupWebService(api.Codecs, path, apiGroup)) m.HandlerContainer.Add(apiserver.NewGroupWebService(api.Codecs, path, apiGroup))

View File

@ -22,13 +22,13 @@ import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/genericapiserver/mux"
) )
// Logs adds handlers for the /logs path serving log files from /var/log. // Logs adds handlers for the /logs path serving log files from /var/log.
type Logs struct{} type Logs struct{}
func (l Logs) Install(mux *apiserver.PathRecorderMux, c *restful.Container) { func (l Logs) Install(c *mux.APIContainer) {
// use restful: ws.Route(ws.GET("/logs/{logpath:*}").To(fileHandler)) // use restful: ws.Route(ws.GET("/logs/{logpath:*}").To(fileHandler))
// See github.com/emicklei/go-restful/blob/master/examples/restful-serve-static.go // See github.com/emicklei/go-restful/blob/master/examples/restful-serve-static.go
ws := new(restful.WebService) ws := new(restful.WebService)

View File

@ -20,28 +20,27 @@ import (
"io" "io"
"net/http" "net/http"
"k8s.io/kubernetes/pkg/apiserver"
apiservermetrics "k8s.io/kubernetes/pkg/apiserver/metrics" apiservermetrics "k8s.io/kubernetes/pkg/apiserver/metrics"
"k8s.io/kubernetes/pkg/genericapiserver/mux"
etcdmetrics "k8s.io/kubernetes/pkg/storage/etcd/metrics" etcdmetrics "k8s.io/kubernetes/pkg/storage/etcd/metrics"
"github.com/emicklei/go-restful"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
// DefaultMetrics installs the default prometheus metrics handler // DefaultMetrics installs the default prometheus metrics handler
type DefaultMetrics struct{} type DefaultMetrics struct{}
func (m DefaultMetrics) Install(mux *apiserver.PathRecorderMux, c *restful.Container) { func (m DefaultMetrics) Install(c *mux.APIContainer) {
mux.HandleFunc("/metrics", prometheus.Handler().ServeHTTP) c.NonSwaggerRoutes.Handle("/metrics", prometheus.Handler())
} }
// MetricsWithReset install the prometheus metrics handler extended with support for the DELETE method // MetricsWithReset install the prometheus metrics handler extended with support for the DELETE method
// which resets the metrics. // which resets the metrics.
type MetricsWithReset struct{} type MetricsWithReset struct{}
func (m MetricsWithReset) Install(mux *apiserver.PathRecorderMux, c *restful.Container) { func (m MetricsWithReset) Install(c *mux.APIContainer) {
defaultMetricsHandler := prometheus.Handler().ServeHTTP defaultMetricsHandler := prometheus.Handler().ServeHTTP
mux.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { c.NonSwaggerRoutes.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
if req.Method == "DELETE" { if req.Method == "DELETE" {
apiservermetrics.Reset() apiservermetrics.Reset()
etcdmetrics.Reset() etcdmetrics.Reset()

View File

@ -19,9 +19,7 @@ package routes
import ( import (
"net/http" "net/http"
"github.com/emicklei/go-restful" "k8s.io/kubernetes/pkg/genericapiserver/mux"
"k8s.io/kubernetes/pkg/apiserver"
) )
const dashboardPath = "/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard" const dashboardPath = "/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard"
@ -29,8 +27,8 @@ const dashboardPath = "/api/v1/proxy/namespaces/kube-system/services/kubernetes-
// UIRediect redirects /ui to the kube-ui proxy path. // UIRediect redirects /ui to the kube-ui proxy path.
type UIRedirect struct{} type UIRedirect struct{}
func (r UIRedirect) Install(mux *apiserver.PathRecorderMux, c *restful.Container) { func (r UIRedirect) Install(c *mux.APIContainer) {
mux.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) { c.NonSwaggerRoutes.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, dashboardPath, http.StatusTemporaryRedirect) http.Redirect(w, r, dashboardPath, http.StatusTemporaryRedirect)
}) })
} }

View File

@ -29,7 +29,6 @@ func TestMasterExportsSymbols(t *testing.T) {
_ = &master.Config{ _ = &master.Config{
GenericConfig: &genericapiserver.Config{ GenericConfig: &genericapiserver.Config{
EnableSwaggerSupport: false, EnableSwaggerSupport: false,
RestfulContainer: nil,
}, },
EnableCoreControllers: false, EnableCoreControllers: false,
EnableUISupport: false, EnableUISupport: false,