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
}
routes.UIRedirect{}.Install(m.Mux, m.HandlerContainer)
routes.Logs{}.Install(m.Mux, m.HandlerContainer)
routes.UIRedirect{}.Install(m.HandlerContainer)
routes.Logs{}.Install(m.HandlerContainer)
restOptionsFactory := restOptionsFactory{
storageFactory: storageFactory,

View File

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

View File

@ -29,7 +29,6 @@ import (
"strings"
"time"
"github.com/emicklei/go-restful"
"github.com/go-openapi/spec"
"github.com/golang/glog"
"gopkg.in/natefinch/lumberjack.v2"
@ -46,6 +45,7 @@ import (
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/cloudprovider"
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/options"
"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
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.
// Note that it is up to the request handlers to ignore or honor this timeout. In seconds.
MinRequestTimeout int
@ -152,6 +149,8 @@ type Config struct {
// Port names should align with ports defined in ExtraServicePorts
ExtraEndpointPorts []api.EndpointPort
// If non-zero, the "kubernetes" services uses this port as NodePort.
// TODO(sttts): move into master
KubernetesServiceNodePort int
// 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
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 {
@ -323,6 +325,9 @@ func (c *Config) Complete() completedConfig {
}
c.ExternalHost = hostAndPort
}
if c.BuildHandlerChainsFunc == nil {
c.BuildHandlerChainsFunc = DefaultBuildHandlerChain
}
return completedConfig{c}
}
@ -391,15 +396,7 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
openAPIDefinitions: c.OpenAPIDefinitions,
}
if c.RestfulContainer != nil {
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)
s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer)
if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil {
s.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{
@ -409,50 +406,50 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
}
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
}
func (s *GenericAPIServer) buildHandlerChains(c *Config, handler http.Handler) (secure http.Handler, 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
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
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) {
if c.EnableIndex {
routes.Index{}.Install(s.Mux, s.HandlerContainer)
routes.Index{}.Install(s.HandlerContainer)
}
if c.EnableSwaggerSupport && c.EnableSwaggerUI {
routes.SwaggerUI{}.Install(s.Mux, s.HandlerContainer)
routes.SwaggerUI{}.Install(s.HandlerContainer)
}
if c.EnableProfiling {
routes.Profiling{}.Install(s.Mux, s.HandlerContainer)
routes.Profiling{}.Install(s.HandlerContainer)
}
if c.EnableVersion {
routes.Version{}.Install(s.Mux, s.HandlerContainer)
routes.Version{}.Install(s.HandlerContainer)
}
s.HandlerContainer.Add(s.DynamicApisDiscovery())
}

View File

@ -42,6 +42,7 @@ import (
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apiserver"
"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/common"
"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 api.RequestContextMapper
Mux *apiserver.PathRecorderMux
HandlerContainer *restful.Container
MasterCount int
// The registered APIs
HandlerContainer *genericmux.APIContainer
SecureServingInfo *ServingInfo
InsecureServingInfo *ServingInfo
@ -128,13 +128,9 @@ type GenericAPIServer struct {
// ExternalAddress is the address (hostname or IP and port) that should be used in
// external (public internet) URLs for this GenericAPIServer.
ExternalAddress string
// ClusterIP is the IP address of the GenericAPIServer within the cluster.
ClusterIP net.IP
PublicReadWritePort int
ServiceReadWriteIP net.IP
ServiceReadWritePort int
ExtraServicePorts []api.ServicePort
ExtraEndpointPorts []api.EndpointPort
ClusterIP net.IP
// storage contains the RESTful endpoints exposed by this GenericAPIServer
storage map[string]rest.Storage
@ -150,26 +146,31 @@ type GenericAPIServer struct {
// Used for custom proxy dialing, and proxy TLS options
ProxyTransport http.RoundTripper
KubernetesServiceNodePort int
// Map storing information about all groups to be exposed in discovery response.
// The map is from name to the group.
apiGroupsForDiscoveryLock sync.RWMutex
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
// 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
postStartHooks map[string]PostStartHookFunc
postStartHookLock sync.Mutex
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() {
@ -191,33 +192,6 @@ func (s *GenericAPIServer) MinRequestTimeout() time.Duration {
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() {
// install APIs which depend on other APIs to be installed
if s.enableSwaggerSupport {
@ -227,7 +201,7 @@ func (s *GenericAPIServer) Run() {
s.InstallOpenAPI()
}
if s.SecureServingInfo != nil {
if s.SecureServingInfo != nil && s.Handler != nil {
secureServer := &http.Server{
Addr: s.SecureServingInfo.BindAddress,
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{
Addr: s.InsecureServingInfo.BindAddress,
Handler: s.InsecureHandler,
@ -343,14 +317,14 @@ func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
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)
}
}
// Install the version handler.
if apiGroupInfo.IsLegacyGroup {
// 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{
ServerAddressByClientCIDRs: s.getServerAddressByClientCIDRs(req.Request),
Versions: apiVersions,
@ -487,7 +461,7 @@ func (s *GenericAPIServer) getSwaggerConfig() *swagger.Config {
// of swagger, so that other resource types show up in the documentation.
func (s *GenericAPIServer) InstallSwaggerAPI() {
// 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.
@ -508,7 +482,7 @@ func (s *GenericAPIServer) InstallOpenAPI() {
Info: &info,
DefaultResponse: &s.openAPIDefaultResponse,
OpenAPIDefinitions: s.openAPIDefinitions,
}, s.HandlerContainer)
}, s.HandlerContainer.Container)
if err != nil {
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,
DefaultResponse: &s.openAPIDefaultResponse,
OpenAPIDefinitions: s.openAPIDefinitions,
}, s.HandlerContainer)
}, s.HandlerContainer.Container)
if err != nil {
glog.Fatalf("Failed to register open api spec for root: %v", err)
}

View File

@ -20,6 +20,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
@ -32,12 +33,11 @@ import (
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
genericmux "k8s.io/kubernetes/pkg/genericapiserver/mux"
ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
utilnet "k8s.io/kubernetes/pkg/util/net"
@ -138,7 +138,7 @@ func TestInstallAPIGroups(t *testing.T) {
s.InstallAPIGroup(&apiGroupsInfo[i])
}
server := httptest.NewServer(s.HandlerContainer.ServeMux)
server := httptest.NewServer(s.InsecureHandler)
defer server.Close()
validPaths := []string{
// "/api"
@ -158,41 +158,64 @@ func TestInstallAPIGroups(t *testing.T) {
}
}
// TestNewHandlerContainer verifies that NewHandlerContainer uses the
// mux provided
func TestNewHandlerContainer(t *testing.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)
// TestCustomHandlerChain verifies the handler chain with custom handler chain builder functions.
func TestCustomHandlerChain(t *testing.T) {
etcdserver, config, _ := setUp(t)
defer etcdserver.Terminate(t)
server := &GenericAPIServer{}
server.Mux = apiserver.NewPathRecorderMux(http.NewServeMux())
handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) }
server.HandleWithAuth("/test", http.HandlerFunc(handler))
var protected, called bool
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
// to the MuxHelper.RegisteredPaths.
func TestHandleFuncWithAuth(t *testing.T) {
etcdserver, _, assert := setUp(t)
defer etcdserver.Terminate(t)
s, err := config.Complete().New()
if err != nil {
t.Fatalf("Error in bringing up the server: %v", err)
}
server := &GenericAPIServer{}
server.Mux = apiserver.NewPathRecorderMux(http.NewServeMux())
handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) }
server.HandleFuncWithAuth("/test", handler)
s.HandlerContainer.NonSwaggerRoutes.Handle("/nonswagger", handler)
s.HandlerContainer.SecretRoutes.Handle("/secret", 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.
@ -267,7 +290,7 @@ func TestInstallSwaggerAPI(t *testing.T) {
mux := http.NewServeMux()
server := &GenericAPIServer{}
server.HandlerContainer = NewHandlerContainer(mux, nil)
server.HandlerContainer = genericmux.NewAPIContainer(mux, nil)
// Ensure swagger isn't installed without the call
ws := server.HandlerContainer.RegisteredWebServices()
@ -286,7 +309,7 @@ func TestInstallSwaggerAPI(t *testing.T) {
// Empty externalHost verification
mux = http.NewServeMux()
server.HandlerContainer = NewHandlerContainer(mux, nil)
server.HandlerContainer = genericmux.NewAPIContainer(mux, nil)
server.ExternalAddress = ""
server.ClusterIP = net.IPv4(10, 10, 10, 10)
server.PublicReadWritePort = 1010
@ -328,7 +351,7 @@ func TestDiscoveryAtAPIS(t *testing.T) {
master, etcdserver, _, assert := newMaster(t)
defer etcdserver.Terminate(t)
server := httptest.NewServer(master.HandlerContainer.ServeMux)
server := httptest.NewServer(master.InsecureHandler)
groupList, err := getGroupList(server)
if err != nil {
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");
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.
*/
package apiserver
package mux
import (
"net/http"

View File

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

View File

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

View File

@ -20,9 +20,8 @@ import (
"net/http"
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"
)
@ -30,12 +29,12 @@ import (
type SwaggerUI struct{}
// 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{
Asset: swagger.Asset,
AssetDir: swagger.AssetDir,
Prefix: "third_party/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"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/genericapiserver/mux"
"k8s.io/kubernetes/pkg/version"
)
@ -29,7 +30,7 @@ import (
type Version struct{}
// 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.
versionWS := new(restful.WebService)
versionWS.Path("/version")

View File

@ -192,10 +192,10 @@ func (c completedConfig) New() (*Master, error) {
}
if c.EnableUISupport {
routes.UIRedirect{}.Install(s.Mux, s.HandlerContainer)
routes.UIRedirect{}.Install(s.HandlerContainer)
}
if c.EnableLogsSupport {
routes.Logs{}.Install(s.Mux, s.HandlerContainer)
routes.Logs{}.Install(s.HandlerContainer)
}
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.",
}, func() float64 { return float64(m.tunneler.SecondsSinceSync()) })
}
healthz.InstallHandler(m.Mux, healthzChecks...)
healthz.InstallHandler(&m.HandlerContainer.NonSwaggerRoutes, healthzChecks...)
if c.GenericConfig.EnableProfiling {
routes.MetricsWithReset{}.Install(m.Mux, m.HandlerContainer)
routes.MetricsWithReset{}.Install(m.HandlerContainer)
} else {
routes.DefaultMetrics{}.Install(m.Mux, m.HandlerContainer)
routes.DefaultMetrics{}.Install(m.HandlerContainer)
}
// Install third party resource support if requested
@ -612,10 +612,10 @@ func (m *Master) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource)
// the group with the new API
if m.hasThirdPartyGroupStorage(path) {
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)
}
m.HandlerContainer.Add(apiserver.NewGroupWebService(api.Codecs, path, apiGroup))

View File

@ -22,13 +22,13 @@ import (
"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.
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))
// See github.com/emicklei/go-restful/blob/master/examples/restful-serve-static.go
ws := new(restful.WebService)

View File

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

View File

@ -19,9 +19,7 @@ package routes
import (
"net/http"
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/genericapiserver/mux"
)
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.
type UIRedirect struct{}
func (r UIRedirect) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
mux.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) {
func (r UIRedirect) Install(c *mux.APIContainer) {
c.NonSwaggerRoutes.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, dashboardPath, http.StatusTemporaryRedirect)
})
}

View File

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