Cleanup non-rest apiserver handlers

- rename MuxHelper -> PathRecorderMux
- move non-rest handlers into routes packages within genericapiserver and
  `pkg/routes` (those from master)
- move ui and logs handlers out of genericapiserver (they are
  not generic)
- make version handler configurable (`config.EnableVersion`)
pull/6/head
Dr. Stefan Schimanski 2016-09-06 13:20:36 +02:00
parent 265746af18
commit 7f78661d0b
27 changed files with 906 additions and 594 deletions

View File

@ -1,10 +1,6 @@
{
"swaggerVersion": "1.2",
"apis": [
{
"path": "/logs",
"description": "get log files"
},
{
"path": "/version",
"description": "git code version from which this is built"
@ -13,6 +9,10 @@
"path": "/apis",
"description": "get available API versions"
},
{
"path": "/logs",
"description": "get log files"
},
{
"path": "/api/v1",
"description": "API at /api/v1"

View File

@ -289,6 +289,8 @@ func Run(s *options.APIServer) error {
DeleteCollectionWorkers: s.DeleteCollectionWorkers,
EventTTL: s.EventTTL,
KubeletClient: kubeletClient,
EnableUISupport: true,
EnableLogsSupport: true,
Tunneler: tunneler,
}

View File

@ -48,6 +48,7 @@ import (
roleetcd "k8s.io/kubernetes/pkg/registry/role/etcd"
"k8s.io/kubernetes/pkg/registry/rolebinding"
rolebindingetcd "k8s.io/kubernetes/pkg/registry/rolebinding/etcd"
"k8s.io/kubernetes/pkg/routes"
"k8s.io/kubernetes/pkg/util/wait"
)
@ -196,6 +197,9 @@ func Run(s *options.ServerRunOptions) error {
return err
}
routes.UIRedirect{}.Install(m.Mux, m.HandlerContainer)
routes.Logs{}.Install(m.Mux, m.HandlerContainer)
installFederationAPIs(s, m, storageFactory)
installCoreAPIs(s, m, storageFactory)
installExtensionsAPIs(s, m, storageFactory)

View File

@ -37,7 +37,7 @@ readonly SWAGGER_PKG="swagger"
function kube::hack::build_ui() {
local pkg="$1"
local src="$2"
local output_file="pkg/ui/data/${pkg}/datafile.go"
local output_file="pkg/genericapiserver/routes/data/${pkg}/datafile.go"
go-bindata -nocompress -o "${output_file}" -prefix ${PWD} -pkg "${pkg}" "${src}"

View File

@ -42,7 +42,6 @@ import (
"k8s.io/kubernetes/pkg/util/flushwriter"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/util/wsstream"
"k8s.io/kubernetes/pkg/version"
"github.com/emicklei/go-restful"
"github.com/golang/glog"
@ -52,12 +51,6 @@ func init() {
metrics.Register()
}
// mux is an object that can register http handlers.
type Mux interface {
Handle(pattern string, handler http.Handler)
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
}
type APIResourceLister interface {
ListAPIResources() []unversioned.APIResource
}
@ -190,48 +183,6 @@ func (g *APIGroupVersion) newInstaller() *APIInstaller {
return installer
}
// TODO: document all handlers
// InstallVersionHandler registers the APIServer's `/version` handler
func InstallVersionHandler(mux Mux, container *restful.Container) {
// Set up a service to return the git code version.
versionWS := new(restful.WebService)
versionWS.Path("/version")
versionWS.Doc("git code version from which this is built")
versionWS.Route(
versionWS.GET("/").To(handleVersion).
Doc("get the code version").
Operation("getCodeVersion").
Produces(restful.MIME_JSON).
Consumes(restful.MIME_JSON).
Writes(version.Info{}))
container.Add(versionWS)
}
// InstallLogsSupport registers the APIServer's `/logs` into a mux.
func InstallLogsSupport(mux Mux, container *restful.Container) {
// 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)
ws.Path("/logs")
ws.Doc("get log files")
ws.Route(ws.GET("/{logpath:*}").To(logFileHandler).Param(ws.PathParameter("logpath", "path to the log").DataType("string")))
ws.Route(ws.GET("/").To(logFileListHandler))
container.Add(ws)
}
func logFileHandler(req *restful.Request, resp *restful.Response) {
logdir := "/var/log"
actual := path.Join(logdir, req.PathParameter("logpath"))
http.ServeFile(resp.ResponseWriter, req.Request, actual)
}
func logFileListHandler(req *restful.Request, resp *restful.Response) {
logdir := "/var/log"
http.ServeFile(resp.ResponseWriter, req.Request, logdir)
}
// TODO: needs to perform response type negotiation, this is probably the wrong way to recover panics
func InstallRecoverHandler(s runtime.NegotiatedSerializer, container *restful.Container) {
container.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
@ -403,11 +354,6 @@ func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful
Writes(unversioned.APIResourceList{}))
}
// handleVersion writes the server's version information.
func handleVersion(req *restful.Request, resp *restful.Response) {
writeRawJSON(http.StatusOK, version.Get(), resp.ResponseWriter)
}
// APIVersionHandler returns a handler which will list the provided versions as available.
func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) restful.RouteFunction {
return func(req *restful.Request, resp *restful.Response) {
@ -484,7 +430,7 @@ func writeNegotiated(s runtime.NegotiatedSerializer, gv unversioned.GroupVersion
serializer, err := negotiateOutputSerializer(req, s)
if err != nil {
status := errToAPIStatus(err)
writeRawJSON(int(status.Code), status, w)
WriteRawJSON(int(status.Code), status, w)
return
}
@ -528,8 +474,8 @@ func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int
return code
}
// writeRawJSON writes a non-API object in JSON.
func writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
// WriteRawJSON writes a non-API object in JSON.
func WriteRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
output, err := json.MarshalIndent(object, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@ -46,7 +46,6 @@ import (
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/diff"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/pkg/watch"
"k8s.io/kubernetes/pkg/watch/versioned"
"k8s.io/kubernetes/plugin/pkg/admission/admit"
@ -317,8 +316,6 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
}
}
InstallVersionHandler(mux, container)
return &defaultAPIServer{mux, container}
}
@ -865,33 +862,6 @@ func TestUnimplementedRESTStorage(t *testing.T) {
}
}
func TestVersion(t *testing.T) {
handler := handle(map[string]rest.Storage{})
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
request, err := http.NewRequest("GET", server.URL+"/version", nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
var info version.Info
err = json.NewDecoder(response.Body).Decode(&info)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(version.Get(), info) {
t.Errorf("Expected %#v, Got %#v", version.Get(), info)
}
}
func TestList(t *testing.T) {
testCases := []struct {
url string
@ -1262,9 +1232,8 @@ func TestMetadata(t *testing.T) {
if matches["text/plain,application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0 ||
matches["application/json,application/json;stream=watch,application/vnd.kubernetes.protobuf,application/vnd.kubernetes.protobuf;stream=watch"] == 0 ||
matches["application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0 ||
matches["application/json"] == 0 ||
matches["*/*"] == 0 ||
len(matches) != 5 {
len(matches) != 4 {
t.Errorf("unexpected mime types: %v", matches)
}
}
@ -2985,7 +2954,7 @@ func (m *marshalError) MarshalJSON() ([]byte, error) {
func TestWriteRAWJSONMarshalError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
writeRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w)
WriteRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w)
}))
defer server.Close()
client := http.Client{}
@ -3270,8 +3239,6 @@ func TestXGSubresource(t *testing.T) {
panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
}
InstallVersionHandler(mux, container)
handler := defaultAPIServer{mux, container}
server := httptest.NewServer(handler)
defer server.Close()

63
pkg/apiserver/mux.go Normal file
View File

@ -0,0 +1,63 @@
/*
Copyright 2015 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 apiserver
import (
"net/http"
)
// Mux is an object that can register http handlers.
type Mux interface {
Handle(pattern string, handler http.Handler)
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
}
// PathRecorderMux wraps a mux object and records the registered paths. It is _not_ go routine safe.
type PathRecorderMux struct {
mux Mux
paths []string
}
// NewPathRecorderMux creates a new PathRecorderMux with the given mux as the base mux.
func NewPathRecorderMux(mux Mux) *PathRecorderMux {
return &PathRecorderMux{
mux: mux,
}
}
// BaseMux returns the underlying mux.
func (m *PathRecorderMux) BaseMux() Mux {
return m.mux
}
// HandledPaths returns the registered handler paths.
func (m *PathRecorderMux) HandledPaths() []string {
return append([]string{}, m.paths...)
}
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (m *PathRecorderMux) Handle(path string, handler http.Handler) {
m.paths = append(m.paths, path)
m.mux.Handle(path, handler)
}
// HandleFunc registers the handler function for the given pattern.
func (m *PathRecorderMux) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
m.paths = append(m.paths, path)
m.mux.HandleFunc(path, handler)
}

View File

@ -19,9 +19,9 @@ package genericapiserver
import (
"crypto/tls"
"fmt"
"mime"
"net"
"net/http"
"net/http/pprof"
"os"
"strconv"
"strings"
@ -42,12 +42,12 @@ import (
"k8s.io/kubernetes/pkg/auth/handlers"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/genericapiserver/options"
"k8s.io/kubernetes/pkg/genericapiserver/routes"
genericvalidation "k8s.io/kubernetes/pkg/genericapiserver/validation"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/registry/generic/registry"
ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/ui"
"k8s.io/kubernetes/pkg/util"
utilnet "k8s.io/kubernetes/pkg/util/net"
)
@ -60,20 +60,18 @@ type Config struct {
AuditLogMaxAge int
AuditLogMaxBackups int
AuditLogMaxSize int
// allow downstream consumers to disable the core controller loops
EnableLogsSupport bool
EnableUISupport bool
// Allow downstream consumers to disable swagger.
// This includes returning the generated swagger spec at /swaggerapi and swagger ui at /swagger-ui.
EnableSwaggerSupport bool
// Allow downstream consumers to disable swagger ui.
// Note that this is ignored if either EnableSwaggerSupport or EnableUISupport is false.
// Note that this is ignored if EnableSwaggerSupport is false
EnableSwaggerUI bool
// Allows api group versions or specific resources to be conditionally enabled/disabled.
APIResourceConfigSource APIResourceConfigSource
// allow downstream consumers to disable the index route
EnableIndex bool
EnableProfiling bool
EnableVersion bool
EnableWatchCache bool
EnableGarbageCollection bool
APIPrefix string
@ -172,11 +170,10 @@ func NewConfig(options *options.ServerRunOptions) *Config {
AuditLogMaxSize: options.AuditLogMaxSize,
EnableGarbageCollection: options.EnableGarbageCollection,
EnableIndex: true,
EnableLogsSupport: options.EnableLogsSupport,
EnableProfiling: options.EnableProfiling,
EnableSwaggerSupport: true,
EnableSwaggerUI: options.EnableSwaggerUI,
EnableUISupport: true,
EnableVersion: true,
EnableWatchCache: options.EnableWatchCache,
ExternalHost: options.ExternalHost,
KubernetesServiceNodePort: options.KubernetesServiceNodePort,
@ -320,16 +317,13 @@ func (c Config) New() (*GenericAPIServer, error) {
}
if c.RestfulContainer != nil {
s.mux = c.RestfulContainer.ServeMux
s.HandlerContainer = c.RestfulContainer
} else {
mux := http.NewServeMux()
s.mux = mux
s.HandlerContainer = NewHandlerContainer(mux, c.Serializer)
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.MuxHelper = &apiserver.MuxHelper{Mux: s.mux, RegisteredPaths: []string{}}
s.Mux = apiserver.NewPathRecorderMux(s.HandlerContainer.ServeMux)
if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil {
s.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{
@ -338,29 +332,29 @@ func (c Config) New() (*GenericAPIServer, error) {
})
}
// Send correct mime type for .svg files.
// TODO: remove when https://github.com/golang/go/commit/21e47d831bafb59f22b1ea8098f709677ec8ce33
// makes it into all of our supported go versions (only in v1.7.1 now).
mime.AddExtensionType(".svg", "image/svg+xml")
// Register root handler.
// We do not register this using restful Webservice since we do not want to surface this in api docs.
// Allow GenericAPIServer to be embedded in contexts which already have something registered at the root
if c.EnableIndex {
s.mux.HandleFunc("/", apiserver.IndexHandler(s.HandlerContainer, s.MuxHelper))
routes.Index{}.Install(s.Mux, s.HandlerContainer)
}
if c.EnableLogsSupport {
apiserver.InstallLogsSupport(s.MuxHelper, s.HandlerContainer)
if c.EnableSwaggerSupport && c.EnableSwaggerUI {
routes.SwaggerUI{}.Install(s.Mux, s.HandlerContainer)
}
if c.EnableUISupport {
ui.InstallSupport(s.MuxHelper, c.EnableSwaggerSupport && c.EnableSwaggerUI)
}
if c.EnableProfiling {
s.mux.HandleFunc("/debug/pprof/", pprof.Index)
s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
routes.Profiling{}.Install(s.Mux, s.HandlerContainer)
}
if c.EnableVersion {
routes.Version{}.Install(s.Mux, s.HandlerContainer)
}
apiserver.InstallVersionHandler(s.MuxHelper, s.HandlerContainer)
handler := http.Handler(s.mux.(*http.ServeMux))
handler := http.Handler(s.Mux.BaseMux().(*http.ServeMux))
// TODO: handle CORS and auth using go-restful
// See github.com/emicklei/go-restful/blob/master/examples/restful-CORS-filter.go, and

View File

@ -123,8 +123,7 @@ type GenericAPIServer struct {
// should be determining the backing storage for the RESTStorage interfaces
storageDecorator generic.StorageDecorator
mux apiserver.Mux
MuxHelper *apiserver.MuxHelper
Mux *apiserver.PathRecorderMux
HandlerContainer *restful.Container
MasterCount int
@ -198,7 +197,7 @@ func (s *GenericAPIServer) HandleWithAuth(pattern string, handler http.Handler)
// sensible policy defaults for plugged-in endpoints. This will be different
// for generic endpoints versus REST object endpoints.
// TODO: convert to go-restful
s.MuxHelper.Handle(pattern, handler)
s.Mux.Handle(pattern, handler)
}
// HandleFuncWithAuth adds an http.Handler for pattern to an http.ServeMux
@ -206,7 +205,7 @@ func (s *GenericAPIServer) HandleWithAuth(pattern string, handler http.Handler)
// 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.MuxHelper.HandleFunc(pattern, handler)
s.Mux.HandleFunc(pattern, handler)
}
func NewHandlerContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *restful.Container {

View File

@ -36,6 +36,8 @@ import (
"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"
ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
utilnet "k8s.io/kubernetes/pkg/util/net"
@ -173,12 +175,11 @@ func TestHandleWithAuth(t *testing.T) {
server, etcdserver, _, assert := setUp(t)
defer etcdserver.Terminate(t)
mh := apiserver.MuxHelper{Mux: http.NewServeMux()}
server.MuxHelper = &mh
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.MuxHelper.RegisteredPaths, "/test", "Path not found in MuxHelper")
assert.Contains(server.Mux.HandledPaths(), "/test", "Path not found in MuxHelper")
}
// TestHandleFuncWithAuth verifies HandleFuncWithAuth adds the path
@ -187,12 +188,78 @@ func TestHandleFuncWithAuth(t *testing.T) {
server, etcdserver, _, assert := setUp(t)
defer etcdserver.Terminate(t)
mh := apiserver.MuxHelper{Mux: http.NewServeMux()}
server.MuxHelper = &mh
server.Mux = apiserver.NewPathRecorderMux(http.NewServeMux())
handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) }
server.HandleFuncWithAuth("/test", handler)
assert.Contains(server.MuxHelper.RegisteredPaths, "/test", "Path not found in MuxHelper")
assert.Contains(server.Mux.HandledPaths(), "/test", "Path not found in MuxHelper")
}
// TestNotRestRoutesHaveAuth checks that special non-routes are behind authz/authn.
func TestNotRestRoutesHaveAuth(t *testing.T) {
_, etcdserver, config, _ := setUp(t)
defer etcdserver.Terminate(t)
authz := mockAuthorizer{}
config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil }
config.ProxyTLSClientConfig = &tls.Config{}
config.APIPrefix = "/apiPrefix"
config.APIGroupPrefix = "/apiGroupPrefix"
config.Serializer = api.Codecs
config.Authorizer = &authz
config.EnableSwaggerUI = true
config.EnableIndex = true
config.EnableProfiling = true
config.EnableSwaggerSupport = true
config.EnableVersion = true
s, err := config.New()
if err != nil {
t.Fatalf("Error in bringing up the server: %v", err)
}
for _, test := range []struct {
route string
}{
{"/"},
{"/swagger-ui/"},
{"/debug/pprof/"},
{"/version"},
} {
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", test.route, nil)
s.Handler.ServeHTTP(resp, req)
if resp.Code != 200 {
t.Errorf("route %q expected to work: code %d", test.route, resp.Code)
continue
}
if authz.lastURI != test.route {
t.Errorf("route %q expected to go through authorization, last route did: %q", test.route, authz.lastURI)
}
}
}
type mockAuthorizer struct {
lastURI string
}
func (authz *mockAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
authz.lastURI = a.GetPath()
return true, "", nil
}
type mockAuthenticator struct {
lastURI string
}
func (authn *mockAuthenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
authn.lastURI = req.RequestURI
return &user.DefaultInfo{
Name: "foo",
}, true, nil
}
// TestInstallSwaggerAPI verifies that the swagger api is added

View File

@ -84,7 +84,6 @@ type ServerRunOptions struct {
AuditLogMaxBackups int
AuditLogMaxSize int
EnableGarbageCollection bool
EnableLogsSupport bool
EnableProfiling bool
EnableSwaggerUI bool
EnableWatchCache bool
@ -135,7 +134,6 @@ func NewServerRunOptions() *ServerRunOptions {
DefaultStorageVersions: registered.AllPreferredGroupVersions(),
DeleteCollectionWorkers: 1,
EnableGarbageCollection: true,
EnableLogsSupport: true,
EnableProfiling: true,
EnableWatchCache: true,
InsecureBindAddress: net.ParseIP("127.0.0.1"),

View File

@ -6,4 +6,7 @@ hack/build-ui.sh
Do not edit by hand.
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/ui/data/README.md?pixel)]()
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/genericapiserver/addons/data/README.md?pixel)]()
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/genericapiserver/routes/data/README.md?pixel)]()

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/*
Copyright 2014 The Kubernetes Authors.
Copyright 2015 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,6 +14,5 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// package ui contains utilities for accessing the static data files compiled in
// the data/* subdirectories.
package ui // import "k8s.io/kubernetes/pkg/ui"
// Package routes holds a collection of optional genericapiserver http handlers.
package routes

View File

@ -14,19 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
package routes
import (
"net/http"
"sort"
"k8s.io/kubernetes/pkg/api/unversioned"
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apiserver"
)
func IndexHandler(container *restful.Container, muxHelper *MuxHelper) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
type Index struct{}
func (i Index) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
mux.BaseMux().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.
@ -35,12 +38,12 @@ func IndexHandler(container *restful.Container, muxHelper *MuxHelper) func(http.
}
var handledPaths []string
// Extract the paths handled using restful.WebService
for _, ws := range container.RegisteredWebServices() {
for _, ws := range c.RegisteredWebServices() {
handledPaths = append(handledPaths, ws.RootPath())
}
// Extract the paths handled using mux handler.
handledPaths = append(handledPaths, muxHelper.RegisteredPaths...)
handledPaths = append(handledPaths, mux.HandledPaths()...)
sort.Strings(handledPaths)
writeRawJSON(status, unversioned.RootPaths{Paths: handledPaths}, w)
}
apiserver.WriteRawJSON(status, unversioned.RootPaths{Paths: handledPaths}, w)
})
}

View File

@ -0,0 +1,33 @@
/*
Copyright 2014 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 routes
import (
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver"
"net/http/pprof"
)
// Profiling adds handlers for pprof under /debug/pprof.
type Profiling struct{}
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)
}

View File

@ -0,0 +1,40 @@
/*
Copyright 2014 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 routes
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/routes/data/swagger"
)
// SwaggerUI exposes files in third_party/swagger-ui/ under /swagger-ui.
type SwaggerUI struct{}
func (l SwaggerUI) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
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))
}

View File

@ -0,0 +1,50 @@
/*
Copyright 2014 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 routes
import (
"net/http"
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/version"
)
type Version struct{}
// InstallVersionHandler registers the APIServer's `/version` handler
func (v Version) Install(mux *apiserver.PathRecorderMux, c *restful.Container) {
// Set up a service to return the git code version.
versionWS := new(restful.WebService)
versionWS.Path("/version")
versionWS.Doc("git code version from which this is built")
versionWS.Route(
versionWS.GET("/").To(handleVersion).
Doc("get the code version").
Operation("getCodeVersion").
Produces(restful.MIME_JSON).
Consumes(restful.MIME_JSON).
Writes(version.Info{}))
c.Add(versionWS)
}
// handleVersion writes the server's version information.
func handleVersion(req *restful.Request, resp *restful.Response) {
apiserver.WriteRawJSON(http.StatusOK, version.Get(), resp.ResponseWriter)
}

View File

@ -18,7 +18,6 @@ package master
import (
"fmt"
"io"
"net"
"net/http"
"net/url"
@ -52,7 +51,6 @@ import (
"k8s.io/kubernetes/pkg/apis/storage"
storageapiv1beta1 "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
"k8s.io/kubernetes/pkg/apiserver"
apiservermetrics "k8s.io/kubernetes/pkg/apiserver/metrics"
"k8s.io/kubernetes/pkg/genericapiserver"
"k8s.io/kubernetes/pkg/healthz"
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
@ -77,22 +75,22 @@ import (
resourcequotaetcd "k8s.io/kubernetes/pkg/registry/resourcequota/etcd"
secretetcd "k8s.io/kubernetes/pkg/registry/secret/etcd"
"k8s.io/kubernetes/pkg/registry/service"
"k8s.io/kubernetes/pkg/registry/service/allocator"
etcdallocator "k8s.io/kubernetes/pkg/registry/service/allocator/etcd"
serviceetcd "k8s.io/kubernetes/pkg/registry/service/etcd"
ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator"
"k8s.io/kubernetes/pkg/registry/service/portallocator"
serviceaccountetcd "k8s.io/kubernetes/pkg/registry/serviceaccount/etcd"
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
thirdpartyresourcedataetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd"
"k8s.io/kubernetes/pkg/routes"
"k8s.io/kubernetes/pkg/runtime"
etcdmetrics "k8s.io/kubernetes/pkg/storage/etcd/metrics"
etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util"
"k8s.io/kubernetes/pkg/storage/storagebackend"
"k8s.io/kubernetes/pkg/util/sets"
"github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/kubernetes/pkg/registry/service/allocator"
"k8s.io/kubernetes/pkg/registry/service/portallocator"
)
const (
@ -112,7 +110,9 @@ type Config struct {
// RESTStorageProviders provides RESTStorage building methods keyed by groupName
RESTStorageProviders map[string]RESTStorageProvider
// Used to start and monitor tunneling
Tunneler genericapiserver.Tunneler
Tunneler genericapiserver.Tunneler
EnableUISupport bool
EnableLogsSupport bool
disableThirdPartyControllerForTesting bool
}
@ -179,11 +179,20 @@ func New(c *Config) (*Master, error) {
return nil, fmt.Errorf("Master.New() called with config.KubeletClient == nil")
}
s, err := c.Config.New()
gc := *c.Config // copy before mutations
gc.EnableSwaggerUI = gc.EnableSwaggerUI && c.EnableUISupport // disable swagger UI if general UI supports it
s, err := gc.New()
if err != nil {
return nil, err
}
if c.EnableUISupport {
routes.UIRedirect{}.Install(s.Mux, s.HandlerContainer)
}
if c.EnableLogsSupport {
routes.Logs{}.Install(s.Mux, s.HandlerContainer)
}
m := &Master{
GenericAPIServer: s,
enableCoreControllers: c.EnableCoreControllers,
@ -220,19 +229,6 @@ func New(c *Config) (*Master, error) {
return m, nil
}
var defaultMetricsHandler = prometheus.Handler().ServeHTTP
// MetricsWithReset is a handler that resets metrics when DELETE is passed to the endpoint.
func MetricsWithReset(w http.ResponseWriter, req *http.Request) {
if req.Method == "DELETE" {
apiservermetrics.Reset()
etcdmetrics.Reset()
io.WriteString(w, "metrics reset\n")
return
}
defaultMetricsHandler(w, req)
}
func (m *Master) InstallAPIs(c *Config) {
apiGroupsInfo := []genericapiserver.APIGroupInfo{}
@ -270,12 +266,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.MuxHelper, healthzChecks...)
healthz.InstallHandler(m.Mux, healthzChecks...)
if c.EnableProfiling {
m.MuxHelper.HandleFunc("/metrics", MetricsWithReset)
routes.MetricsWithReset{}.Install(m.Mux, m.HandlerContainer)
} else {
m.MuxHelper.HandleFunc("/metrics", defaultMetricsHandler)
routes.DefaultMetrics{}.Install(m.Mux, m.HandlerContainer)
}
// Install third party resource support if requested

View File

@ -63,6 +63,7 @@ import (
"k8s.io/kubernetes/pkg/util/intstr"
utilnet "k8s.io/kubernetes/pkg/util/net"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/version"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
@ -104,6 +105,7 @@ func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.
config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil }
config.ProxyTLSClientConfig = &tls.Config{}
config.RequestContextMapper = api.NewRequestContextMapper()
config.Config.EnableVersion = true
// TODO: this is kind of hacky. The trouble is that the sync loop
// runs in a go-routine and there is no way to validate in the test
@ -203,6 +205,29 @@ func TestNamespaceSubresources(t *testing.T) {
}
}
// TestVersion tests /version
func TestVersion(t *testing.T) {
s, etcdserver, _, _ := newMaster(t)
defer etcdserver.Terminate(t)
req, _ := http.NewRequest("GET", "/version", nil)
resp := httptest.NewRecorder()
s.InsecureHandler.ServeHTTP(resp, req)
if resp.Code != 200 {
t.Fatalf("expected http 200, got: %d", resp.Code)
}
var info version.Info
err := json.NewDecoder(resp.Body).Decode(&info)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(version.Get(), info) {
t.Errorf("Expected %#v, Got %#v", version.Get(), info)
}
}
// TestGetServersToValidate verifies the unexported getServersToValidate function
func TestGetServersToValidate(t *testing.T) {
master, etcdserver, config, assert := setUp(t)

View File

@ -14,24 +14,5 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"net/http"
)
// Offers additional functionality over ServeMux, for ex: supports listing registered paths.
type MuxHelper struct {
Mux Mux
RegisteredPaths []string
}
func (m *MuxHelper) Handle(path string, handler http.Handler) {
m.RegisteredPaths = append(m.RegisteredPaths, path)
m.Mux.Handle(path, handler)
}
func (m *MuxHelper) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
m.RegisteredPaths = append(m.RegisteredPaths, path)
m.Mux.HandleFunc(path, handler)
}
// Package routes holds a collection of optional master http handlers.
package routes

52
pkg/routes/logs.go Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright 2014 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 routes
import (
"net/http"
"path"
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver"
)
// 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) {
// 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)
ws.Path("/logs")
ws.Doc("get log files")
ws.Route(ws.GET("/{logpath:*}").To(logFileHandler).Param(ws.PathParameter("logpath", "path to the log").DataType("string")))
ws.Route(ws.GET("/").To(logFileListHandler))
c.Add(ws)
}
func logFileHandler(req *restful.Request, resp *restful.Response) {
logdir := "/var/log"
actual := path.Join(logdir, req.PathParameter("logpath"))
http.ServeFile(resp.ResponseWriter, req.Request, actual)
}
func logFileListHandler(req *restful.Request, resp *restful.Response) {
logdir := "/var/log"
http.ServeFile(resp.ResponseWriter, req.Request, logdir)
}

53
pkg/routes/metrics.go Normal file
View File

@ -0,0 +1,53 @@
/*
Copyright 2014 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 routes
import (
"io"
"net/http"
"k8s.io/kubernetes/pkg/apiserver"
apiservermetrics "k8s.io/kubernetes/pkg/apiserver/metrics"
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)
}
// 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) {
defaultMetricsHandler := prometheus.Handler().ServeHTTP
mux.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
if req.Method == "DELETE" {
apiservermetrics.Reset()
etcdmetrics.Reset()
io.WriteString(w, "metrics reset\n")
return
}
defaultMetricsHandler(w, req)
})
}

36
pkg/routes/ui.go Normal file
View File

@ -0,0 +1,36 @@
/*
Copyright 2014 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 routes
import (
"net/http"
"github.com/emicklei/go-restful"
"k8s.io/kubernetes/pkg/apiserver"
)
const dashboardPath = "/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard"
// 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) {
http.Redirect(w, r, dashboardPath, http.StatusTemporaryRedirect)
})
}

View File

@ -1,58 +0,0 @@
/*
Copyright 2014 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 ui
import (
"mime"
"net/http"
"k8s.io/kubernetes/pkg/ui/data/swagger"
assetfs "github.com/elazarl/go-bindata-assetfs"
)
const dashboardPath = "/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard"
type MuxInterface interface {
Handle(pattern string, handler http.Handler)
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
}
func InstallSupport(mux MuxInterface, enableSwaggerSupport bool) {
// Send correct mime type for .svg files. TODO: remove when
// https://github.com/golang/go/commit/21e47d831bafb59f22b1ea8098f709677ec8ce33
// makes it into all of our supported go versions.
mime.AddExtensionType(".svg", "image/svg+xml")
// Redirect /ui to the kube-ui proxy path
prefix := "/ui/"
mux.HandleFunc(prefix, func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, dashboardPath, http.StatusTemporaryRedirect)
})
if enableSwaggerSupport {
// Expose files in third_party/swagger-ui/ on <host>/swagger-ui
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))
}
}

View File

@ -229,6 +229,7 @@ func NewMasterConfig() *master.Config {
// Set those values to avoid annoying warnings in logs.
ServiceClusterIPRange: parseCIDROrDie("10.0.0.0/24"),
ServiceNodePortRange: utilnet.PortRange{Base: 30000, Size: 2768},
EnableVersion: true,
},
KubeletClient: kubeletclient.FakeKubeletClient{},
}
@ -239,6 +240,7 @@ func NewIntegrationTestMasterConfig() *master.Config {
masterConfig := NewMasterConfig()
masterConfig.EnableCoreControllers = true
masterConfig.EnableIndex = true
masterConfig.EnableVersion = true
masterConfig.PublicAddress = net.ParseIP("192.168.10.4")
masterConfig.APIResourceConfigSource = master.DefaultAPIResourceConfigSource()
return masterConfig

View File

@ -28,11 +28,12 @@ import (
func TestMasterExportsSymbols(t *testing.T) {
_ = &master.Config{
Config: &genericapiserver.Config{
EnableUISupport: false,
EnableSwaggerSupport: false,
RestfulContainer: nil,
},
EnableCoreControllers: false,
EnableUISupport: false,
EnableLogsSupport: false,
}
m := &master.Master{
GenericAPIServer: &genericapiserver.GenericAPIServer{},