Commit 7f78661d authored by Dr. Stefan Schimanski's avatar Dr. Stefan Schimanski

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`)
parent 265746af
......@@ -2,10 +2,6 @@
"swaggerVersion": "1.2",
"apis": [
{
"path": "/logs",
"description": "get log files"
},
{
"path": "/version",
"description": "git code version from which this is built"
},
......@@ -14,6 +10,10 @@
"description": "get available API versions"
},
{
"path": "/logs",
"description": "get log files"
},
{
"path": "/api/v1",
"description": "API at /api/v1"
},
......
......@@ -289,6 +289,8 @@ func Run(s *options.APIServer) error {
DeleteCollectionWorkers: s.DeleteCollectionWorkers,
EventTTL: s.EventTTL,
KubeletClient: kubeletClient,
EnableUISupport: true,
EnableLogsSupport: true,
Tunneler: tunneler,
}
......
......@@ -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)
......
......@@ -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}"
......
......@@ -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)
......
......@@ -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()
......
/*
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)
}
......@@ -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.EnableUISupport {
ui.InstallSupport(s.MuxHelper, c.EnableSwaggerSupport && c.EnableSwaggerUI)
if c.EnableSwaggerSupport && c.EnableSwaggerUI {
routes.SwaggerUI{}.Install(s.Mux, s.HandlerContainer)
}
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
......
......@@ -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 {
......
......@@ -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
......
......@@ -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"),
......
......@@ -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)]()
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
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
......@@ -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)
})
}
/*
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)
}
......@@ -14,45 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package ui
package routes
import (
"mime"
"net/http"
"k8s.io/kubernetes/pkg/ui/data/swagger"
assetfs "github.com/elazarl/go-bindata-assetfs"
)
"github.com/emicklei/go-restful"
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) {
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/genericapiserver/routes/data/swagger"
)
// 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")
// SwaggerUI exposes files in third_party/swagger-ui/ under /swagger-ui.
type SwaggerUI struct{}
// 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)
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",
})
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))
}
prefix := "/swagger-ui/"
mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
}
/*
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)
}
......@@ -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
......
......@@ -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)
......
......@@ -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
/*
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)
}
/*
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)
})
}
/*
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)
})
}
......@@ -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
......
......@@ -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{},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment