mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 18:24:07 +00:00
Merge pull request #38875 from sttts/sttts-move-pkg-apiserver
Automatic merge from submit-queue pkg/apiserver: split up monolithic package **Based on** https://github.com/kubernetes/kubernetes/pull/38191 This is a first step to integrate pkg/apiserver with pkg/genericapiserver into a common package structure. For this - pkg/apiserver is cleaned up from code which does not belong there, - split up into pkg/apiserver, pkg/apiserver/handlers{,/negotation,/helpers,/errors}
This commit is contained in:
commit
e3a4a3675b
@ -30,8 +30,8 @@ go_library(
|
|||||||
"//pkg/api/errors:go_default_library",
|
"//pkg/api/errors:go_default_library",
|
||||||
"//pkg/api/rest:go_default_library",
|
"//pkg/api/rest:go_default_library",
|
||||||
"//pkg/apis/meta/v1:go_default_library",
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
"//pkg/apiserver:go_default_library",
|
|
||||||
"//pkg/apiserver/filters:go_default_library",
|
"//pkg/apiserver/filters:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/responsewriters:go_default_library",
|
||||||
"//pkg/auth/handlers:go_default_library",
|
"//pkg/auth/handlers:go_default_library",
|
||||||
"//pkg/client/cache:go_default_library",
|
"//pkg/client/cache:go_default_library",
|
||||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
"k8s.io/kubernetes/pkg/apiserver"
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
v1listers "k8s.io/kubernetes/pkg/client/listers/core/v1"
|
v1listers "k8s.io/kubernetes/pkg/client/listers/core/v1"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
@ -187,7 +187,7 @@ func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
apiServices, err := r.lister.List(labels.Everything())
|
apiServices, err := r.lister.List(labels.Everything())
|
||||||
if statusErr, ok := err.(*apierrors.StatusError); ok && err != nil {
|
if statusErr, ok := err.(*apierrors.StatusError); ok && err != nil {
|
||||||
apiserver.WriteRawJSON(int(statusErr.Status().Code), statusErr.Status(), w)
|
responsewriters.WriteRawJSON(int(statusErr.Status().Code), statusErr.Status(), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apiserver"
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
"k8s.io/kubernetes/pkg/client/transport"
|
"k8s.io/kubernetes/pkg/client/transport"
|
||||||
genericrest "k8s.io/kubernetes/pkg/registry/generic/rest"
|
genericrest "k8s.io/kubernetes/pkg/registry/generic/rest"
|
||||||
@ -154,7 +154,7 @@ type responder struct {
|
|||||||
// TODO this should properly handle content type negotiation
|
// TODO this should properly handle content type negotiation
|
||||||
// if the caller asked for protobuf and you write JSON bad things happen.
|
// if the caller asked for protobuf and you write JSON bad things happen.
|
||||||
func (r *responder) Object(statusCode int, obj runtime.Object) {
|
func (r *responder) Object(statusCode int, obj runtime.Object) {
|
||||||
apiserver.WriteRawJSON(statusCode, obj, r.w)
|
responsewriters.WriteRawJSON(statusCode, obj, r.w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responder) Error(err error) {
|
func (r *responder) Error(err error) {
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
apiserverrequest "k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
"k8s.io/kubernetes/pkg/auth/user"
|
"k8s.io/kubernetes/pkg/auth/user"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
|
|
||||||
@ -62,13 +62,13 @@ func (m *fakeRequestContextMapper) Get(req *http.Request) (api.Context, bool) {
|
|||||||
ctx = api.WithUser(ctx, m.user)
|
ctx = api.WithUser(ctx, m.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
resolver := &request.RequestInfoFactory{
|
resolver := &apiserverrequest.RequestInfoFactory{
|
||||||
APIPrefixes: sets.NewString("api", "apis"),
|
APIPrefixes: sets.NewString("api", "apis"),
|
||||||
GrouplessAPIPrefixes: sets.NewString("api"),
|
GrouplessAPIPrefixes: sets.NewString("api"),
|
||||||
}
|
}
|
||||||
info, err := resolver.NewRequestInfo(req)
|
info, err := resolver.NewRequestInfo(req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ctx = request.WithRequestInfo(ctx, info)
|
ctx = apiserverrequest.WithRequestInfo(ctx, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx, true
|
return ctx, true
|
||||||
|
@ -95,6 +95,7 @@ pkg/apis/rbac/install
|
|||||||
pkg/apis/storage/install
|
pkg/apis/storage/install
|
||||||
pkg/apis/storage/validation
|
pkg/apis/storage/validation
|
||||||
pkg/apiserver/audit
|
pkg/apiserver/audit
|
||||||
|
pkg/apiserver/handlers/responsewriters
|
||||||
pkg/apiserver/openapi
|
pkg/apiserver/openapi
|
||||||
pkg/auth/authenticator
|
pkg/auth/authenticator
|
||||||
pkg/auth/authorizer/union
|
pkg/auth/authorizer/union
|
||||||
|
@ -11,15 +11,11 @@ load(
|
|||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"api_installer.go",
|
|
||||||
"apiserver.go",
|
"apiserver.go",
|
||||||
|
"discovery.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"errors.go",
|
"groupversion.go",
|
||||||
"negotiate.go",
|
"installer.go",
|
||||||
"proxy.go",
|
|
||||||
"resthandler.go",
|
|
||||||
"serviceerror.go",
|
|
||||||
"watch.go",
|
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
@ -30,46 +26,25 @@ go_library(
|
|||||||
"//pkg/api/rest:go_default_library",
|
"//pkg/api/rest:go_default_library",
|
||||||
"//pkg/apis/extensions:go_default_library",
|
"//pkg/apis/extensions:go_default_library",
|
||||||
"//pkg/apis/meta/v1:go_default_library",
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/negotiation:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/responsewriters:go_default_library",
|
||||||
"//pkg/apiserver/metrics:go_default_library",
|
"//pkg/apiserver/metrics:go_default_library",
|
||||||
"//pkg/apiserver/request:go_default_library",
|
|
||||||
"//pkg/conversion:go_default_library",
|
"//pkg/conversion:go_default_library",
|
||||||
"//pkg/fields:go_default_library",
|
|
||||||
"//pkg/httplog:go_default_library",
|
|
||||||
"//pkg/runtime:go_default_library",
|
"//pkg/runtime:go_default_library",
|
||||||
"//pkg/runtime/schema:go_default_library",
|
"//pkg/runtime/schema:go_default_library",
|
||||||
"//pkg/runtime/serializer/streaming:go_default_library",
|
|
||||||
"//pkg/storage:go_default_library",
|
|
||||||
"//pkg/util:go_default_library",
|
|
||||||
"//pkg/util/errors:go_default_library",
|
"//pkg/util/errors:go_default_library",
|
||||||
"//pkg/util/flushwriter:go_default_library",
|
|
||||||
"//pkg/util/httpstream:go_default_library",
|
|
||||||
"//pkg/util/net:go_default_library",
|
|
||||||
"//pkg/util/proxy:go_default_library",
|
|
||||||
"//pkg/util/runtime:go_default_library",
|
|
||||||
"//pkg/util/sets:go_default_library",
|
|
||||||
"//pkg/util/strategicpatch:go_default_library",
|
|
||||||
"//pkg/util/strings:go_default_library",
|
"//pkg/util/strings:go_default_library",
|
||||||
"//pkg/util/version:go_default_library",
|
|
||||||
"//pkg/util/wsstream:go_default_library",
|
|
||||||
"//pkg/watch:go_default_library",
|
|
||||||
"//pkg/watch/versioned:go_default_library",
|
|
||||||
"//vendor:bitbucket.org/ww/goautoneg",
|
|
||||||
"//vendor:github.com/emicklei/go-restful",
|
"//vendor:github.com/emicklei/go-restful",
|
||||||
"//vendor:github.com/evanphx/json-patch",
|
|
||||||
"//vendor:github.com/golang/glog",
|
|
||||||
"//vendor:golang.org/x/net/websocket",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"api_installer_test.go",
|
|
||||||
"apiserver_test.go",
|
"apiserver_test.go",
|
||||||
"errors_test.go",
|
"installer_test.go",
|
||||||
"negotiate_test.go",
|
|
||||||
"proxy_test.go",
|
"proxy_test.go",
|
||||||
"resthandler_test.go",
|
|
||||||
"watch_test.go",
|
"watch_test.go",
|
||||||
],
|
],
|
||||||
library = "go_default_library",
|
library = "go_default_library",
|
||||||
@ -80,11 +55,12 @@ go_test(
|
|||||||
"//pkg/api/errors:go_default_library",
|
"//pkg/api/errors:go_default_library",
|
||||||
"//pkg/api/meta:go_default_library",
|
"//pkg/api/meta:go_default_library",
|
||||||
"//pkg/api/rest:go_default_library",
|
"//pkg/api/rest:go_default_library",
|
||||||
"//pkg/api/testapi:go_default_library",
|
|
||||||
"//pkg/api/testing:go_default_library",
|
"//pkg/api/testing:go_default_library",
|
||||||
"//pkg/api/v1:go_default_library",
|
"//pkg/api/v1:go_default_library",
|
||||||
"//pkg/apis/meta/v1:go_default_library",
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
"//pkg/apiserver/filters:go_default_library",
|
"//pkg/apiserver/filters:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/responsewriters:go_default_library",
|
||||||
"//pkg/apiserver/request:go_default_library",
|
"//pkg/apiserver/request:go_default_library",
|
||||||
"//pkg/apiserver/testing:go_default_library",
|
"//pkg/apiserver/testing:go_default_library",
|
||||||
"//pkg/fields:go_default_library",
|
"//pkg/fields:go_default_library",
|
||||||
@ -92,18 +68,15 @@ go_test(
|
|||||||
"//pkg/runtime:go_default_library",
|
"//pkg/runtime:go_default_library",
|
||||||
"//pkg/runtime/schema:go_default_library",
|
"//pkg/runtime/schema:go_default_library",
|
||||||
"//pkg/runtime/serializer/streaming:go_default_library",
|
"//pkg/runtime/serializer/streaming:go_default_library",
|
||||||
"//pkg/types:go_default_library",
|
|
||||||
"//pkg/util/diff:go_default_library",
|
"//pkg/util/diff:go_default_library",
|
||||||
"//pkg/util/net:go_default_library",
|
"//pkg/util/net:go_default_library",
|
||||||
"//pkg/util/sets:go_default_library",
|
"//pkg/util/sets:go_default_library",
|
||||||
"//pkg/util/strategicpatch:go_default_library",
|
|
||||||
"//pkg/util/wait:go_default_library",
|
"//pkg/util/wait:go_default_library",
|
||||||
"//pkg/watch:go_default_library",
|
"//pkg/watch:go_default_library",
|
||||||
"//pkg/watch/versioned:go_default_library",
|
"//pkg/watch/versioned:go_default_library",
|
||||||
"//plugin/pkg/admission/admit:go_default_library",
|
"//plugin/pkg/admission/admit:go_default_library",
|
||||||
"//plugin/pkg/admission/deny:go_default_library",
|
"//plugin/pkg/admission/deny:go_default_library",
|
||||||
"//vendor:github.com/emicklei/go-restful",
|
"//vendor:github.com/emicklei/go-restful",
|
||||||
"//vendor:github.com/evanphx/json-patch",
|
|
||||||
"//vendor:golang.org/x/net/websocket",
|
"//vendor:golang.org/x/net/websocket",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -17,502 +17,9 @@ limitations under the License.
|
|||||||
package apiserver
|
package apiserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
rt "runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/admission"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
|
||||||
"k8s.io/kubernetes/pkg/api/meta"
|
|
||||||
"k8s.io/kubernetes/pkg/api/rest"
|
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/apiserver/metrics"
|
"k8s.io/kubernetes/pkg/apiserver/metrics"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
|
||||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
|
||||||
utilerrors "k8s.io/kubernetes/pkg/util/errors"
|
|
||||||
"k8s.io/kubernetes/pkg/util/flushwriter"
|
|
||||||
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
|
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
|
||||||
utilversion "k8s.io/kubernetes/pkg/util/version"
|
|
||||||
"k8s.io/kubernetes/pkg/util/wsstream"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
metrics.Register()
|
metrics.Register()
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIResourceLister interface {
|
|
||||||
ListAPIResources() []metav1.APIResource
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
|
||||||
// It handles URLs of the form:
|
|
||||||
// /${storage_key}[/${object_name}]
|
|
||||||
// Where 'storage_key' points to a rest.Storage object stored in storage.
|
|
||||||
// This object should contain all parameterization necessary for running a particular API version
|
|
||||||
type APIGroupVersion struct {
|
|
||||||
Storage map[string]rest.Storage
|
|
||||||
|
|
||||||
Root string
|
|
||||||
|
|
||||||
// GroupVersion is the external group version
|
|
||||||
GroupVersion schema.GroupVersion
|
|
||||||
|
|
||||||
// OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver
|
|
||||||
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
|
|
||||||
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
|
|
||||||
// empty, defaults to GroupVersion.
|
|
||||||
OptionsExternalVersion *schema.GroupVersion
|
|
||||||
|
|
||||||
Mapper meta.RESTMapper
|
|
||||||
|
|
||||||
// Serializer is used to determine how to convert responses from API methods into bytes to send over
|
|
||||||
// the wire.
|
|
||||||
Serializer runtime.NegotiatedSerializer
|
|
||||||
ParameterCodec runtime.ParameterCodec
|
|
||||||
|
|
||||||
Typer runtime.ObjectTyper
|
|
||||||
Creater runtime.ObjectCreater
|
|
||||||
Convertor runtime.ObjectConvertor
|
|
||||||
Copier runtime.ObjectCopier
|
|
||||||
Linker runtime.SelfLinker
|
|
||||||
|
|
||||||
Admit admission.Interface
|
|
||||||
Context api.RequestContextMapper
|
|
||||||
|
|
||||||
MinRequestTimeout time.Duration
|
|
||||||
|
|
||||||
// SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is
|
|
||||||
// accessible from this API group version. The GroupVersionKind is that of the external version of
|
|
||||||
// the subresource. The key of this map should be the path of the subresource. The keys here should
|
|
||||||
// match the keys in the Storage map above for subresources.
|
|
||||||
SubresourceGroupVersionKind map[string]schema.GroupVersionKind
|
|
||||||
|
|
||||||
// ResourceLister is an interface that knows how to list resources
|
|
||||||
// for this API Group.
|
|
||||||
ResourceLister APIResourceLister
|
|
||||||
}
|
|
||||||
|
|
||||||
// staticLister implements the APIResourceLister interface
|
|
||||||
type staticLister struct {
|
|
||||||
list []metav1.APIResource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s staticLister) ListAPIResources() []metav1.APIResource {
|
|
||||||
return s.list
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ APIResourceLister = &staticLister{}
|
|
||||||
|
|
||||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
|
||||||
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
|
|
||||||
// in a slash.
|
|
||||||
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
|
||||||
installer := g.newInstaller()
|
|
||||||
ws := installer.NewWebService()
|
|
||||||
apiResources, registrationErrors := installer.Install(ws)
|
|
||||||
lister := g.ResourceLister
|
|
||||||
if lister == nil {
|
|
||||||
lister = staticLister{apiResources}
|
|
||||||
}
|
|
||||||
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
|
||||||
container.Add(ws)
|
|
||||||
return utilerrors.NewAggregate(registrationErrors)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateREST registers the REST handlers for this APIGroupVersion to an existing web service
|
|
||||||
// in the restful Container. It will use the prefix (root/version) to find the existing
|
|
||||||
// web service. If a web service does not exist within the container to support the prefix
|
|
||||||
// this method will return an error.
|
|
||||||
func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
|
|
||||||
installer := g.newInstaller()
|
|
||||||
var ws *restful.WebService = nil
|
|
||||||
|
|
||||||
for i, s := range container.RegisteredWebServices() {
|
|
||||||
if s.RootPath() == installer.prefix {
|
|
||||||
ws = container.RegisteredWebServices()[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ws == nil {
|
|
||||||
return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix))
|
|
||||||
}
|
|
||||||
apiResources, registrationErrors := installer.Install(ws)
|
|
||||||
lister := g.ResourceLister
|
|
||||||
if lister == nil {
|
|
||||||
lister = staticLister{apiResources}
|
|
||||||
}
|
|
||||||
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
|
||||||
return utilerrors.NewAggregate(registrationErrors)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST.
|
|
||||||
func (g *APIGroupVersion) newInstaller() *APIInstaller {
|
|
||||||
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
|
|
||||||
installer := &APIInstaller{
|
|
||||||
group: g,
|
|
||||||
prefix: prefix,
|
|
||||||
minRequestTimeout: g.MinRequestTimeout,
|
|
||||||
}
|
|
||||||
return installer
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
logStackOnRecover(s, panicReason, httpWriter)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Unify with RecoverPanics?
|
|
||||||
func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
|
|
||||||
for i := 2; ; i += 1 {
|
|
||||||
_, file, line, ok := rt.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
|
|
||||||
}
|
|
||||||
glog.Errorln(buffer.String())
|
|
||||||
|
|
||||||
headers := http.Header{}
|
|
||||||
if ct := w.Header().Get("Content-Type"); len(ct) > 0 {
|
|
||||||
headers.Set("Accept", ct)
|
|
||||||
}
|
|
||||||
errorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", api.Resource(""), "", "", 0, false), s, schema.GroupVersion{}, w, &http.Request{Header: headers})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a service to return the supported api versions at the legacy /api.
|
|
||||||
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *metav1.APIVersions) {
|
|
||||||
// TODO: InstallREST should register each version automatically
|
|
||||||
|
|
||||||
// Because in release 1.1, /api returns response with empty APIVersion, we
|
|
||||||
// use StripVersionNegotiatedSerializer to keep the response backwards
|
|
||||||
// compatible.
|
|
||||||
mediaTypes, _ := mediaTypesForSerializer(s)
|
|
||||||
ss := StripVersionNegotiatedSerializer{s}
|
|
||||||
versionHandler := APIVersionHandler(ss, getAPIVersionsFunc)
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path(apiPrefix)
|
|
||||||
ws.Doc("get available API versions")
|
|
||||||
ws.Route(ws.GET("/").To(versionHandler).
|
|
||||||
Doc("get available API versions").
|
|
||||||
Operation("getAPIVersions").
|
|
||||||
Produces(mediaTypes...).
|
|
||||||
Consumes(mediaTypes...).
|
|
||||||
Writes(metav1.APIVersions{}))
|
|
||||||
container.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stripVersionEncoder strips APIVersion field from the encoding output. It's
|
|
||||||
// used to keep the responses at the discovery endpoints backward compatible
|
|
||||||
// with release-1.1, when the responses have empty APIVersion.
|
|
||||||
type stripVersionEncoder struct {
|
|
||||||
encoder runtime.Encoder
|
|
||||||
serializer runtime.Serializer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c stripVersionEncoder) Encode(obj runtime.Object, w io.Writer) error {
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
|
||||||
err := c.encoder.Encode(obj, buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gvk.Group = ""
|
|
||||||
gvk.Version = ""
|
|
||||||
roundTrippedObj.GetObjectKind().SetGroupVersionKind(*gvk)
|
|
||||||
return c.serializer.Encode(roundTrippedObj, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StripVersionNegotiatedSerializer will return stripVersionEncoder when
|
|
||||||
// EncoderForVersion is called. See comments for stripVersionEncoder.
|
|
||||||
type StripVersionNegotiatedSerializer struct {
|
|
||||||
runtime.NegotiatedSerializer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n StripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
|
||||||
serializer, ok := encoder.(runtime.Serializer)
|
|
||||||
if !ok {
|
|
||||||
// The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
|
|
||||||
// decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
|
|
||||||
// decoder.
|
|
||||||
panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
|
|
||||||
}
|
|
||||||
versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
|
|
||||||
return stripVersionEncoder{versioned, serializer}
|
|
||||||
}
|
|
||||||
|
|
||||||
func keepUnversioned(group string) bool {
|
|
||||||
return group == "" || group == "extensions"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApisWebService returns a webservice serving the available api version under /apis.
|
|
||||||
func NewApisWebService(s runtime.NegotiatedSerializer, apiPrefix string, f func(req *restful.Request) []metav1.APIGroup) *restful.WebService {
|
|
||||||
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
|
||||||
// use StripVersionNegotiatedSerializer to keep the response backwards
|
|
||||||
// compatible.
|
|
||||||
ss := StripVersionNegotiatedSerializer{s}
|
|
||||||
mediaTypes, _ := mediaTypesForSerializer(s)
|
|
||||||
rootAPIHandler := RootAPIHandler(ss, f)
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path(apiPrefix)
|
|
||||||
ws.Doc("get available API versions")
|
|
||||||
ws.Route(ws.GET("/").To(rootAPIHandler).
|
|
||||||
Doc("get available API versions").
|
|
||||||
Operation("getAPIVersions").
|
|
||||||
Produces(mediaTypes...).
|
|
||||||
Consumes(mediaTypes...).
|
|
||||||
Writes(metav1.APIGroupList{}))
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGroupWebService returns a webservice serving the supported versions, preferred version, and name
|
|
||||||
// of a group. E.g., such a web service will be registered at /apis/extensions.
|
|
||||||
func NewGroupWebService(s runtime.NegotiatedSerializer, path string, group metav1.APIGroup) *restful.WebService {
|
|
||||||
ss := s
|
|
||||||
if keepUnversioned(group.Name) {
|
|
||||||
// Because in release 1.1, /apis/extensions returns response with empty
|
|
||||||
// APIVersion, we use StripVersionNegotiatedSerializer to keep the
|
|
||||||
// response backwards compatible.
|
|
||||||
ss = StripVersionNegotiatedSerializer{s}
|
|
||||||
}
|
|
||||||
mediaTypes, _ := mediaTypesForSerializer(s)
|
|
||||||
groupHandler := GroupHandler(ss, group)
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path(path)
|
|
||||||
ws.Doc("get information of a group")
|
|
||||||
ws.Route(ws.GET("/").To(groupHandler).
|
|
||||||
Doc("get information of a group").
|
|
||||||
Operation("getAPIGroup").
|
|
||||||
Produces(mediaTypes...).
|
|
||||||
Consumes(mediaTypes...).
|
|
||||||
Writes(metav1.APIGroup{}))
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a service to return the supported resources, E.g., a such web service
|
|
||||||
// will be registered at /apis/extensions/v1.
|
|
||||||
func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful.WebService, groupVersion schema.GroupVersion, lister APIResourceLister) {
|
|
||||||
ss := s
|
|
||||||
if keepUnversioned(groupVersion.Group) {
|
|
||||||
// Because in release 1.1, /apis/extensions/v1beta1 returns response
|
|
||||||
// with empty APIVersion, we use StripVersionNegotiatedSerializer to
|
|
||||||
// keep the response backwards compatible.
|
|
||||||
ss = StripVersionNegotiatedSerializer{s}
|
|
||||||
}
|
|
||||||
mediaTypes, _ := mediaTypesForSerializer(s)
|
|
||||||
resourceHandler := SupportedResourcesHandler(ss, groupVersion, lister)
|
|
||||||
ws.Route(ws.GET("/").To(resourceHandler).
|
|
||||||
Doc("get available resources").
|
|
||||||
Operation("getAPIResources").
|
|
||||||
Produces(mediaTypes...).
|
|
||||||
Consumes(mediaTypes...).
|
|
||||||
Writes(metav1.APIResourceList{}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIVersionHandler returns a handler which will list the provided versions as available.
|
|
||||||
func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *metav1.APIVersions) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
|
||||||
writeNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, getAPIVersionsFunc(req))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove in 1.6. Returns if kubectl is older than v1.5.0
|
|
||||||
func isOldKubectl(userAgent string) bool {
|
|
||||||
// example userAgent string: kubectl-1.3/v1.3.8 (linux/amd64) kubernetes/e328d5b
|
|
||||||
if !strings.Contains(userAgent, "kubectl") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
userAgent = strings.Split(userAgent, " ")[0]
|
|
||||||
subs := strings.Split(userAgent, "/")
|
|
||||||
if len(subs) != 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
kubectlVersion, versionErr := utilversion.ParseSemantic(subs[1])
|
|
||||||
if versionErr != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return kubectlVersion.LessThan(utilversion.MustParseSemantic("v1.5.0"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove in 1.6. This is for backward compatibility with 1.4 kubectl.
|
|
||||||
// See https://github.com/kubernetes/kubernetes/issues/35791
|
|
||||||
var groupsWithNewVersionsIn1_5 = sets.NewString("apps", "policy")
|
|
||||||
|
|
||||||
// TODO: Remove in 1.6.
|
|
||||||
func filterAPIGroups(req *restful.Request, groups []metav1.APIGroup) []metav1.APIGroup {
|
|
||||||
if !isOldKubectl(req.HeaderParameter("User-Agent")) {
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
// hide API group that has new versions added in 1.5.
|
|
||||||
var ret []metav1.APIGroup
|
|
||||||
for _, group := range groups {
|
|
||||||
if groupsWithNewVersionsIn1_5.Has(group.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ret = append(ret, group)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// RootAPIHandler returns a handler which will list the provided groups and versions as available.
|
|
||||||
func RootAPIHandler(s runtime.NegotiatedSerializer, f func(req *restful.Request) []metav1.APIGroup) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
|
||||||
writeNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &metav1.APIGroupList{Groups: filterAPIGroups(req, f(req))})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupHandler returns a handler which will return the api.GroupAndVersion of
|
|
||||||
// the group.
|
|
||||||
func GroupHandler(s runtime.NegotiatedSerializer, group metav1.APIGroup) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
|
||||||
writeNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportedResourcesHandler returns a handler which will list the provided resources as available.
|
|
||||||
func SupportedResourcesHandler(s runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, lister APIResourceLister) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
|
||||||
writeNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &metav1.APIResourceList{GroupVersion: groupVersion.String(), APIResources: lister.ListAPIResources()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object
|
|
||||||
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
|
|
||||||
// response. The Accept header and current API version will be passed in, and the output will be copied
|
|
||||||
// directly to the response body. If content type is returned it is used, otherwise the content type will
|
|
||||||
// be "application/octet-stream". All other objects are sent to standard JSON serialization.
|
|
||||||
func write(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
|
||||||
stream, ok := object.(rest.ResourceStreamer)
|
|
||||||
if !ok {
|
|
||||||
writeNegotiated(s, gv, w, req, statusCode, object)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept"))
|
|
||||||
if err != nil {
|
|
||||||
errorNegotiated(err, s, gv, w, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if out == nil {
|
|
||||||
// No output provided - return StatusNoContent
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
if wsstream.IsWebSocketRequest(req) {
|
|
||||||
r := wsstream.NewReader(out, true, wsstream.NewDefaultReaderProtocols())
|
|
||||||
if err := r.Copy(w, req); err != nil {
|
|
||||||
utilruntime.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(contentType) == 0 {
|
|
||||||
contentType = "application/octet-stream"
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
|
||||||
w.WriteHeader(statusCode)
|
|
||||||
writer := w.(io.Writer)
|
|
||||||
if flush {
|
|
||||||
writer = flushwriter.Wrap(w)
|
|
||||||
}
|
|
||||||
io.Copy(writer, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeNegotiated renders an object in the content type negotiated by the client
|
|
||||||
func writeNegotiated(s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
|
||||||
serializer, err := negotiateOutputSerializer(req, s)
|
|
||||||
if err != nil {
|
|
||||||
status := errToAPIStatus(err)
|
|
||||||
WriteRawJSON(int(status.Code), status, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", serializer.MediaType)
|
|
||||||
w.WriteHeader(statusCode)
|
|
||||||
|
|
||||||
encoder := s.EncoderForVersion(serializer.Serializer, gv)
|
|
||||||
if err := encoder.Encode(object, w); err != nil {
|
|
||||||
errorJSONFatal(err, encoder, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorNegotiated renders an error to the response. Returns the HTTP status code of the error.
|
|
||||||
func errorNegotiated(err error, s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request) int {
|
|
||||||
status := errToAPIStatus(err)
|
|
||||||
code := int(status.Code)
|
|
||||||
// when writing an error, check to see if the status indicates a retry after period
|
|
||||||
if status.Details != nil && status.Details.RetryAfterSeconds > 0 {
|
|
||||||
delay := strconv.Itoa(int(status.Details.RetryAfterSeconds))
|
|
||||||
w.Header().Set("Retry-After", delay)
|
|
||||||
}
|
|
||||||
writeNegotiated(s, gv, w, req, code, status)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorJSONFatal renders an error to the response, and if codec fails will render plaintext.
|
|
||||||
// Returns the HTTP status code of the error.
|
|
||||||
func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int {
|
|
||||||
utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
|
|
||||||
status := errToAPIStatus(err)
|
|
||||||
code := int(status.Code)
|
|
||||||
output, err := runtime.Encode(codec, status)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(code)
|
|
||||||
fmt.Fprintf(w, "%s: %s", status.Reason, status.Message)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(code)
|
|
||||||
w.Write(output)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(statusCode)
|
|
||||||
w.Write(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTimeout(str string) time.Duration {
|
|
||||||
if str != "" {
|
|
||||||
timeout, err := time.ParseDuration(str)
|
|
||||||
if err == nil {
|
|
||||||
return timeout
|
|
||||||
}
|
|
||||||
glog.Errorf("Failed to parse %q: %v", str, err)
|
|
||||||
}
|
|
||||||
return 30 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
func readBody(req *http.Request) ([]byte, error) {
|
|
||||||
defer req.Body.Close()
|
|
||||||
return ioutil.ReadAll(req.Body)
|
|
||||||
}
|
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -38,10 +39,12 @@ import (
|
|||||||
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/meta"
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
"k8s.io/kubernetes/pkg/api/rest"
|
"k8s.io/kubernetes/pkg/api/rest"
|
||||||
|
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/filters"
|
"k8s.io/kubernetes/pkg/apiserver/filters"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing"
|
apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing"
|
||||||
"k8s.io/kubernetes/pkg/fields"
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
@ -58,10 +61,6 @@ import (
|
|||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
)
|
)
|
||||||
|
|
||||||
func convert(obj runtime.Object) (runtime.Object, error) {
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This creates fake API versions, similar to api/latest.go.
|
// This creates fake API versions, similar to api/latest.go.
|
||||||
var testAPIGroup = "test.group"
|
var testAPIGroup = "test.group"
|
||||||
var testAPIGroup2 = "test.group2"
|
var testAPIGroup2 = "test.group2"
|
||||||
@ -79,12 +78,10 @@ var grouplessPrefix = "api"
|
|||||||
var groupVersions = []schema.GroupVersion{grouplessGroupVersion, testGroupVersion, newGroupVersion}
|
var groupVersions = []schema.GroupVersion{grouplessGroupVersion, testGroupVersion, newGroupVersion}
|
||||||
|
|
||||||
var codec = api.Codecs.LegacyCodec(groupVersions...)
|
var codec = api.Codecs.LegacyCodec(groupVersions...)
|
||||||
var grouplessCodec = api.Codecs.LegacyCodec(grouplessGroupVersion)
|
|
||||||
var testCodec = api.Codecs.LegacyCodec(testGroupVersion)
|
var testCodec = api.Codecs.LegacyCodec(testGroupVersion)
|
||||||
var newCodec = api.Codecs.LegacyCodec(newGroupVersion)
|
var newCodec = api.Codecs.LegacyCodec(newGroupVersion)
|
||||||
|
|
||||||
var accessor = meta.NewAccessor()
|
var accessor = meta.NewAccessor()
|
||||||
var versioner runtime.ResourceVersioner = accessor
|
|
||||||
var selfLinker runtime.SelfLinker = accessor
|
var selfLinker runtime.SelfLinker = accessor
|
||||||
var mapper, namespaceMapper meta.RESTMapper // The mappers with namespace and with legacy namespace scopes.
|
var mapper, namespaceMapper meta.RESTMapper // The mappers with namespace and with legacy namespace scopes.
|
||||||
var admissionControl admission.Interface
|
var admissionControl admission.Interface
|
||||||
@ -2773,18 +2770,6 @@ func TestUpdateChecksDecode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTimeout(t *testing.T) {
|
|
||||||
if d := parseTimeout(""); d != 30*time.Second {
|
|
||||||
t.Errorf("blank timeout produces %v", d)
|
|
||||||
}
|
|
||||||
if d := parseTimeout("not a timeout"); d != 30*time.Second {
|
|
||||||
t.Errorf("bad timeout produces %v", d)
|
|
||||||
}
|
|
||||||
if d := parseTimeout("10s"); d != 10*time.Second {
|
|
||||||
t.Errorf("10s timeout produced: %v", d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type setTestSelfLinker struct {
|
type setTestSelfLinker struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
expectedSet string
|
expectedSet string
|
||||||
@ -3089,7 +3074,7 @@ func (obj *UnregisteredAPIObject) GetObjectKind() schema.ObjectKind {
|
|||||||
|
|
||||||
func TestWriteJSONDecodeError(t *testing.T) {
|
func TestWriteJSONDecodeError(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
writeNegotiated(api.Codecs, newGroupVersion, w, req, http.StatusOK, &UnregisteredAPIObject{"Undecodable"})
|
responsewriters.WriteObjectNegotiated(api.Codecs, newGroupVersion, w, req, http.StatusOK, &UnregisteredAPIObject{"Undecodable"})
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
// We send a 200 status code before we encode the object, so we expect OK, but there will
|
// We send a 200 status code before we encode the object, so we expect OK, but there will
|
||||||
@ -3114,7 +3099,7 @@ func (m *marshalError) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
func TestWriteRAWJSONMarshalError(t *testing.T) {
|
func TestWriteRAWJSONMarshalError(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
WriteRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w)
|
responsewriters.WriteRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w)
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
@ -3425,3 +3410,15 @@ func newTestRequestInfoResolver() *request.RequestInfoFactory {
|
|||||||
GrouplessAPIPrefixes: sets.NewString("api"),
|
GrouplessAPIPrefixes: sets.NewString("api"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const benchmarkSeed = 100
|
||||||
|
|
||||||
|
func benchmarkItems() []api.Pod {
|
||||||
|
apiObjectFuzzer := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed))
|
||||||
|
items := make([]api.Pod, 3)
|
||||||
|
for i := range items {
|
||||||
|
apiObjectFuzzer.Fuzz(&items[i])
|
||||||
|
items[i].Spec.InitContainers, items[i].Status.InitContainerStatuses = nil, nil
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
172
pkg/apiserver/discovery.go
Normal file
172
pkg/apiserver/discovery.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
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 apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/negotiation"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddApiWebService adds a service to return the supported api versions at the legacy /api.
|
||||||
|
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *metav1.APIVersions) {
|
||||||
|
// TODO: InstallREST should register each version automatically
|
||||||
|
|
||||||
|
// Because in release 1.1, /api returns response with empty APIVersion, we
|
||||||
|
// use StripVersionNegotiatedSerializer to keep the response backwards
|
||||||
|
// compatible.
|
||||||
|
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
||||||
|
ss := stripVersionNegotiatedSerializer{s}
|
||||||
|
versionHandler := APIVersionHandler(ss, getAPIVersionsFunc)
|
||||||
|
ws := new(restful.WebService)
|
||||||
|
ws.Path(apiPrefix)
|
||||||
|
ws.Doc("get available API versions")
|
||||||
|
ws.Route(ws.GET("/").To(versionHandler).
|
||||||
|
Doc("get available API versions").
|
||||||
|
Operation("getAPIVersions").
|
||||||
|
Produces(mediaTypes...).
|
||||||
|
Consumes(mediaTypes...).
|
||||||
|
Writes(metav1.APIVersions{}))
|
||||||
|
container.Add(ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripVersionEncoder strips APIVersion field from the encoding output. It's
|
||||||
|
// used to keep the responses at the discovery endpoints backward compatible
|
||||||
|
// with release-1.1, when the responses have empty APIVersion.
|
||||||
|
type stripVersionEncoder struct {
|
||||||
|
encoder runtime.Encoder
|
||||||
|
serializer runtime.Serializer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c stripVersionEncoder) Encode(obj runtime.Object, w io.Writer) error {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := c.encoder.Encode(obj, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gvk.Group = ""
|
||||||
|
gvk.Version = ""
|
||||||
|
roundTrippedObj.GetObjectKind().SetGroupVersionKind(*gvk)
|
||||||
|
return c.serializer.Encode(roundTrippedObj, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripVersionNegotiatedSerializer will return stripVersionEncoder when
|
||||||
|
// EncoderForVersion is called. See comments for stripVersionEncoder.
|
||||||
|
type stripVersionNegotiatedSerializer struct {
|
||||||
|
runtime.NegotiatedSerializer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
||||||
|
serializer, ok := encoder.(runtime.Serializer)
|
||||||
|
if !ok {
|
||||||
|
// The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
|
||||||
|
// decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
|
||||||
|
// decoder.
|
||||||
|
panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
|
||||||
|
}
|
||||||
|
versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
|
||||||
|
return stripVersionEncoder{versioned, serializer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keepUnversioned(group string) bool {
|
||||||
|
return group == "" || group == "extensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApisWebService returns a webservice serving the available api version under /apis.
|
||||||
|
func NewApisWebService(s runtime.NegotiatedSerializer, apiPrefix string, f func(req *restful.Request) []metav1.APIGroup) *restful.WebService {
|
||||||
|
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
||||||
|
// use StripVersionNegotiatedSerializer to keep the response backwards
|
||||||
|
// compatible.
|
||||||
|
ss := stripVersionNegotiatedSerializer{s}
|
||||||
|
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
||||||
|
rootAPIHandler := handlers.RootAPIHandler(ss, f)
|
||||||
|
ws := new(restful.WebService)
|
||||||
|
ws.Path(apiPrefix)
|
||||||
|
ws.Doc("get available API versions")
|
||||||
|
ws.Route(ws.GET("/").To(rootAPIHandler).
|
||||||
|
Doc("get available API versions").
|
||||||
|
Operation("getAPIVersions").
|
||||||
|
Produces(mediaTypes...).
|
||||||
|
Consumes(mediaTypes...).
|
||||||
|
Writes(metav1.APIGroupList{}))
|
||||||
|
return ws
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGroupWebService returns a webservice serving the supported versions, preferred version, and name
|
||||||
|
// of a group. E.g., such a web service will be registered at /apis/extensions.
|
||||||
|
func NewGroupWebService(s runtime.NegotiatedSerializer, path string, group metav1.APIGroup) *restful.WebService {
|
||||||
|
ss := s
|
||||||
|
if keepUnversioned(group.Name) {
|
||||||
|
// Because in release 1.1, /apis/extensions returns response with empty
|
||||||
|
// APIVersion, we use StripVersionNegotiatedSerializer to keep the
|
||||||
|
// response backwards compatible.
|
||||||
|
ss = stripVersionNegotiatedSerializer{s}
|
||||||
|
}
|
||||||
|
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
||||||
|
groupHandler := handlers.GroupHandler(ss, group)
|
||||||
|
ws := new(restful.WebService)
|
||||||
|
ws.Path(path)
|
||||||
|
ws.Doc("get information of a group")
|
||||||
|
ws.Route(ws.GET("/").To(groupHandler).
|
||||||
|
Doc("get information of a group").
|
||||||
|
Operation("getAPIGroup").
|
||||||
|
Produces(mediaTypes...).
|
||||||
|
Consumes(mediaTypes...).
|
||||||
|
Writes(metav1.APIGroup{}))
|
||||||
|
return ws
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a service to return the supported resources, E.g., a such web service
|
||||||
|
// will be registered at /apis/extensions/v1.
|
||||||
|
func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful.WebService, groupVersion schema.GroupVersion, lister handlers.APIResourceLister) {
|
||||||
|
ss := s
|
||||||
|
if keepUnversioned(groupVersion.Group) {
|
||||||
|
// Because in release 1.1, /apis/extensions/v1beta1 returns response
|
||||||
|
// with empty APIVersion, we use StripVersionNegotiatedSerializer to
|
||||||
|
// keep the response backwards compatible.
|
||||||
|
ss = stripVersionNegotiatedSerializer{s}
|
||||||
|
}
|
||||||
|
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
||||||
|
resourceHandler := handlers.SupportedResourcesHandler(ss, groupVersion, lister)
|
||||||
|
ws.Route(ws.GET("/").To(resourceHandler).
|
||||||
|
Doc("get available resources").
|
||||||
|
Operation("getAPIResources").
|
||||||
|
Produces(mediaTypes...).
|
||||||
|
Consumes(mediaTypes...).
|
||||||
|
Writes(metav1.APIResourceList{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIVersionHandler returns a handler which will list the provided versions as available.
|
||||||
|
func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *metav1.APIVersions) restful.RouteFunction {
|
||||||
|
return func(req *restful.Request, resp *restful.Response) {
|
||||||
|
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, getAPIVersionsFunc(req))
|
||||||
|
}
|
||||||
|
}
|
149
pkg/apiserver/groupversion.go
Normal file
149
pkg/apiserver/groupversion.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
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 apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/admission"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||||
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
|
"k8s.io/kubernetes/pkg/api/rest"
|
||||||
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
|
utilerrors "k8s.io/kubernetes/pkg/util/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
||||||
|
// It handles URLs of the form:
|
||||||
|
// /${storage_key}[/${object_name}]
|
||||||
|
// Where 'storage_key' points to a rest.Storage object stored in storage.
|
||||||
|
// This object should contain all parameterization necessary for running a particular API version
|
||||||
|
type APIGroupVersion struct {
|
||||||
|
Storage map[string]rest.Storage
|
||||||
|
|
||||||
|
Root string
|
||||||
|
|
||||||
|
// GroupVersion is the external group version
|
||||||
|
GroupVersion schema.GroupVersion
|
||||||
|
|
||||||
|
// OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver
|
||||||
|
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
|
||||||
|
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
|
||||||
|
// empty, defaults to GroupVersion.
|
||||||
|
OptionsExternalVersion *schema.GroupVersion
|
||||||
|
|
||||||
|
Mapper meta.RESTMapper
|
||||||
|
|
||||||
|
// Serializer is used to determine how to convert responses from API methods into bytes to send over
|
||||||
|
// the wire.
|
||||||
|
Serializer runtime.NegotiatedSerializer
|
||||||
|
ParameterCodec runtime.ParameterCodec
|
||||||
|
|
||||||
|
Typer runtime.ObjectTyper
|
||||||
|
Creater runtime.ObjectCreater
|
||||||
|
Convertor runtime.ObjectConvertor
|
||||||
|
Copier runtime.ObjectCopier
|
||||||
|
Linker runtime.SelfLinker
|
||||||
|
|
||||||
|
Admit admission.Interface
|
||||||
|
Context api.RequestContextMapper
|
||||||
|
|
||||||
|
MinRequestTimeout time.Duration
|
||||||
|
|
||||||
|
// SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is
|
||||||
|
// accessible from this API group version. The GroupVersionKind is that of the external version of
|
||||||
|
// the subresource. The key of this map should be the path of the subresource. The keys here should
|
||||||
|
// match the keys in the Storage map above for subresources.
|
||||||
|
SubresourceGroupVersionKind map[string]schema.GroupVersionKind
|
||||||
|
|
||||||
|
// ResourceLister is an interface that knows how to list resources
|
||||||
|
// for this API Group.
|
||||||
|
ResourceLister handlers.APIResourceLister
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||||
|
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
|
||||||
|
// in a slash.
|
||||||
|
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
||||||
|
installer := g.newInstaller()
|
||||||
|
ws := installer.NewWebService()
|
||||||
|
apiResources, registrationErrors := installer.Install(ws)
|
||||||
|
lister := g.ResourceLister
|
||||||
|
if lister == nil {
|
||||||
|
lister = staticLister{apiResources}
|
||||||
|
}
|
||||||
|
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
||||||
|
container.Add(ws)
|
||||||
|
return utilerrors.NewAggregate(registrationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateREST registers the REST handlers for this APIGroupVersion to an existing web service
|
||||||
|
// in the restful Container. It will use the prefix (root/version) to find the existing
|
||||||
|
// web service. If a web service does not exist within the container to support the prefix
|
||||||
|
// this method will return an error.
|
||||||
|
func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
|
||||||
|
installer := g.newInstaller()
|
||||||
|
var ws *restful.WebService = nil
|
||||||
|
|
||||||
|
for i, s := range container.RegisteredWebServices() {
|
||||||
|
if s.RootPath() == installer.prefix {
|
||||||
|
ws = container.RegisteredWebServices()[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ws == nil {
|
||||||
|
return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix))
|
||||||
|
}
|
||||||
|
apiResources, registrationErrors := installer.Install(ws)
|
||||||
|
lister := g.ResourceLister
|
||||||
|
if lister == nil {
|
||||||
|
lister = staticLister{apiResources}
|
||||||
|
}
|
||||||
|
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
||||||
|
return utilerrors.NewAggregate(registrationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST.
|
||||||
|
func (g *APIGroupVersion) newInstaller() *APIInstaller {
|
||||||
|
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
|
||||||
|
installer := &APIInstaller{
|
||||||
|
group: g,
|
||||||
|
prefix: prefix,
|
||||||
|
minRequestTimeout: g.MinRequestTimeout,
|
||||||
|
}
|
||||||
|
return installer
|
||||||
|
}
|
||||||
|
|
||||||
|
// staticLister implements the APIResourceLister interface
|
||||||
|
type staticLister struct {
|
||||||
|
list []metav1.APIResource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s staticLister) ListAPIResources() []metav1.APIResource {
|
||||||
|
return s.list
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ handlers.APIResourceLister = &staticLister{}
|
75
pkg/apiserver/handlers/BUILD
Normal file
75
pkg/apiserver/handlers/BUILD
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["resthandler_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/api/errors:go_default_library",
|
||||||
|
"//pkg/api/rest:go_default_library",
|
||||||
|
"//pkg/api/testapi:go_default_library",
|
||||||
|
"//pkg/api/v1:go_default_library",
|
||||||
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/runtime:go_default_library",
|
||||||
|
"//pkg/runtime/schema:go_default_library",
|
||||||
|
"//pkg/types:go_default_library",
|
||||||
|
"//pkg/util/diff:go_default_library",
|
||||||
|
"//pkg/util/strategicpatch:go_default_library",
|
||||||
|
"//vendor:github.com/emicklei/go-restful",
|
||||||
|
"//vendor:github.com/evanphx/json-patch",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"discovery.go",
|
||||||
|
"doc.go",
|
||||||
|
"proxy.go",
|
||||||
|
"resthandler.go",
|
||||||
|
"watch.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/admission:go_default_library",
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/api/errors:go_default_library",
|
||||||
|
"//pkg/api/meta:go_default_library",
|
||||||
|
"//pkg/api/rest:go_default_library",
|
||||||
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/negotiation:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/responsewriters:go_default_library",
|
||||||
|
"//pkg/apiserver/metrics:go_default_library",
|
||||||
|
"//pkg/apiserver/request:go_default_library",
|
||||||
|
"//pkg/fields:go_default_library",
|
||||||
|
"//pkg/httplog:go_default_library",
|
||||||
|
"//pkg/runtime:go_default_library",
|
||||||
|
"//pkg/runtime/schema:go_default_library",
|
||||||
|
"//pkg/runtime/serializer/streaming:go_default_library",
|
||||||
|
"//pkg/util:go_default_library",
|
||||||
|
"//pkg/util/httpstream:go_default_library",
|
||||||
|
"//pkg/util/net:go_default_library",
|
||||||
|
"//pkg/util/proxy:go_default_library",
|
||||||
|
"//pkg/util/runtime:go_default_library",
|
||||||
|
"//pkg/util/sets:go_default_library",
|
||||||
|
"//pkg/util/strategicpatch:go_default_library",
|
||||||
|
"//pkg/util/version:go_default_library",
|
||||||
|
"//pkg/util/wsstream:go_default_library",
|
||||||
|
"//pkg/watch:go_default_library",
|
||||||
|
"//pkg/watch/versioned:go_default_library",
|
||||||
|
"//vendor:github.com/emicklei/go-restful",
|
||||||
|
"//vendor:github.com/evanphx/json-patch",
|
||||||
|
"//vendor:github.com/golang/glog",
|
||||||
|
"//vendor:golang.org/x/net/websocket",
|
||||||
|
],
|
||||||
|
)
|
95
pkg/apiserver/handlers/discovery.go
Normal file
95
pkg/apiserver/handlers/discovery.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
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 handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
|
utilversion "k8s.io/kubernetes/pkg/util/version"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIResourceLister interface {
|
||||||
|
ListAPIResources() []metav1.APIResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootAPIHandler returns a handler which will list the provided groups and versions as available.
|
||||||
|
func RootAPIHandler(s runtime.NegotiatedSerializer, f func(req *restful.Request) []metav1.APIGroup) restful.RouteFunction {
|
||||||
|
return func(req *restful.Request, resp *restful.Response) {
|
||||||
|
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &metav1.APIGroupList{Groups: filterAPIGroups(req, f(req))})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupHandler returns a handler which will return the api.GroupAndVersion of
|
||||||
|
// the group.
|
||||||
|
func GroupHandler(s runtime.NegotiatedSerializer, group metav1.APIGroup) restful.RouteFunction {
|
||||||
|
return func(req *restful.Request, resp *restful.Response) {
|
||||||
|
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportedResourcesHandler returns a handler which will list the provided resources as available.
|
||||||
|
func SupportedResourcesHandler(s runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, lister APIResourceLister) restful.RouteFunction {
|
||||||
|
return func(req *restful.Request, resp *restful.Response) {
|
||||||
|
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &metav1.APIResourceList{GroupVersion: groupVersion.String(), APIResources: lister.ListAPIResources()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove in 1.6. This is for backward compatibility with 1.4 kubectl.
|
||||||
|
// See https://github.com/kubernetes/kubernetes/issues/35791
|
||||||
|
var groupsWithNewVersionsIn1_5 = sets.NewString("apps", "policy")
|
||||||
|
|
||||||
|
// TODO: Remove in 1.6.
|
||||||
|
func filterAPIGroups(req *restful.Request, groups []metav1.APIGroup) []metav1.APIGroup {
|
||||||
|
if !isOldKubectl(req.HeaderParameter("User-Agent")) {
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
// hide API group that has new versions added in 1.5.
|
||||||
|
var ret []metav1.APIGroup
|
||||||
|
for _, group := range groups {
|
||||||
|
if groupsWithNewVersionsIn1_5.Has(group.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, group)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove in 1.6. Returns if kubectl is older than v1.5.0
|
||||||
|
func isOldKubectl(userAgent string) bool {
|
||||||
|
// example userAgent string: kubectl-1.3/v1.3.8 (linux/amd64) kubernetes/e328d5b
|
||||||
|
if !strings.Contains(userAgent, "kubectl") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
userAgent = strings.Split(userAgent, " ")[0]
|
||||||
|
subs := strings.Split(userAgent, "/")
|
||||||
|
if len(subs) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
kubectlVersion, versionErr := utilversion.ParseSemantic(subs[1])
|
||||||
|
if versionErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return kubectlVersion.LessThan(utilversion.MustParseSemantic("v1.5.0"))
|
||||||
|
}
|
18
pkg/apiserver/handlers/doc.go
Normal file
18
pkg/apiserver/handlers/doc.go
Normal 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 handlers contains HTTP handlers to implement the apiserver APIs.
|
||||||
|
package handlers // import "k8s.io/kubernetes/pkg/apiserver/handlers"
|
36
pkg/apiserver/handlers/errors/BUILD
Normal file
36
pkg/apiserver/handlers/errors/BUILD
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["errors_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/api/errors:go_default_library",
|
||||||
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/runtime/schema:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"errors.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/storage:go_default_library",
|
||||||
|
"//pkg/util/runtime:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
18
pkg/apiserver/handlers/errors/doc.go
Normal file
18
pkg/apiserver/handlers/errors/doc.go
Normal 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 errors contains HTTP handler related errors
|
||||||
|
package errors // import "k8s.io/kubernetes/pkg/apiserver/handlers/errors"
|
@ -14,12 +14,11 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
"k8s.io/kubernetes/pkg/storage"
|
"k8s.io/kubernetes/pkg/storage"
|
||||||
@ -31,8 +30,8 @@ type statusError interface {
|
|||||||
Status() metav1.Status
|
Status() metav1.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// errToAPIStatus converts an error to an metav1.Status object.
|
// ErrToAPIStatus converts an error to an metav1.Status object.
|
||||||
func errToAPIStatus(err error) *metav1.Status {
|
func ErrToAPIStatus(err error) *metav1.Status {
|
||||||
switch t := err.(type) {
|
switch t := err.(type) {
|
||||||
case statusError:
|
case statusError:
|
||||||
status := t.Status()
|
status := t.Status()
|
||||||
@ -70,19 +69,6 @@ func errToAPIStatus(err error) *metav1.Status {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// notFound renders a simple not found error.
|
|
||||||
func notFound(w http.ResponseWriter, req *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
fmt.Fprintf(w, "Not Found: %#v", req.RequestURI)
|
|
||||||
}
|
|
||||||
|
|
||||||
// internalError renders a simple internal error
|
|
||||||
func internalError(w http.ResponseWriter, req *http.Request, err error) {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
fmt.Fprintf(w, "Internal Server Error: %#v", req.RequestURI)
|
|
||||||
runtime.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under
|
// errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under
|
||||||
// any known API prefixes
|
// any known API prefixes
|
||||||
type errAPIPrefixNotFound struct {
|
type errAPIPrefixNotFound struct {
|
||||||
@ -101,41 +87,3 @@ func IsAPIPrefixNotFound(err error) bool {
|
|||||||
_, ok := err.(*errAPIPrefixNotFound)
|
_, ok := err.(*errAPIPrefixNotFound)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// errNotAcceptable indicates Accept negotiation has failed
|
|
||||||
// TODO: move to api/errors if other code needs to return this
|
|
||||||
type errNotAcceptable struct {
|
|
||||||
accepted []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e errNotAcceptable) Error() string {
|
|
||||||
return fmt.Sprintf("only the following media types are accepted: %v", strings.Join(e.accepted, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e errNotAcceptable) Status() metav1.Status {
|
|
||||||
return metav1.Status{
|
|
||||||
Status: metav1.StatusFailure,
|
|
||||||
Code: http.StatusNotAcceptable,
|
|
||||||
Reason: metav1.StatusReason("NotAcceptable"),
|
|
||||||
Message: e.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// errUnsupportedMediaType indicates Content-Type is not recognized
|
|
||||||
// TODO: move to api/errors if other code needs to return this
|
|
||||||
type errUnsupportedMediaType struct {
|
|
||||||
accepted []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e errUnsupportedMediaType) Error() string {
|
|
||||||
return fmt.Sprintf("the body of the request was in an unknown format - accepted media types include: %v", strings.Join(e.accepted, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e errUnsupportedMediaType) Status() metav1.Status {
|
|
||||||
return metav1.Status{
|
|
||||||
Status: metav1.StatusFailure,
|
|
||||||
Code: http.StatusUnsupportedMediaType,
|
|
||||||
Reason: metav1.StatusReason("UnsupportedMediaType"),
|
|
||||||
Message: e.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
stderrs "errors"
|
stderrs "errors"
|
||||||
@ -65,7 +65,7 @@ func TestErrorsToAPIStatus(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for k, v := range cases {
|
for k, v := range cases {
|
||||||
actual := errToAPIStatus(k)
|
actual := ErrToAPIStatus(k)
|
||||||
if !reflect.DeepEqual(actual, &v) {
|
if !reflect.DeepEqual(actual, &v) {
|
||||||
t.Errorf("%s: Expected %#v, Got %#v", k, v, actual)
|
t.Errorf("%s: Expected %#v, Got %#v", k, v, actual)
|
||||||
}
|
}
|
36
pkg/apiserver/handlers/negotiation/BUILD
Normal file
36
pkg/apiserver/handlers/negotiation/BUILD
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["negotiate_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/runtime:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"errors.go",
|
||||||
|
"negotiate.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/runtime:go_default_library",
|
||||||
|
"//pkg/runtime/schema:go_default_library",
|
||||||
|
"//vendor:bitbucket.org/ww/goautoneg",
|
||||||
|
],
|
||||||
|
)
|
18
pkg/apiserver/handlers/negotiation/doc.go
Normal file
18
pkg/apiserver/handlers/negotiation/doc.go
Normal 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 negotation contains media type negotiation logic.
|
||||||
|
package negotiation // import "k8s.io/kubernetes/pkg/apiserver/handlers/negotiation"
|
61
pkg/apiserver/handlers/negotiation/errors.go
Normal file
61
pkg/apiserver/handlers/negotiation/errors.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
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 negotiation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errNotAcceptable indicates Accept negotiation has failed
|
||||||
|
type errNotAcceptable struct {
|
||||||
|
accepted []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errNotAcceptable) Error() string {
|
||||||
|
return fmt.Sprintf("only the following media types are accepted: %v", strings.Join(e.accepted, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errNotAcceptable) Status() metav1.Status {
|
||||||
|
return metav1.Status{
|
||||||
|
Status: metav1.StatusFailure,
|
||||||
|
Code: http.StatusNotAcceptable,
|
||||||
|
Reason: metav1.StatusReason("NotAcceptable"),
|
||||||
|
Message: e.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errUnsupportedMediaType indicates Content-Type is not recognized
|
||||||
|
type errUnsupportedMediaType struct {
|
||||||
|
accepted []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errUnsupportedMediaType) Error() string {
|
||||||
|
return fmt.Sprintf("the body of the request was in an unknown format - accepted media types include: %v", strings.Join(e.accepted, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errUnsupportedMediaType) Status() metav1.Status {
|
||||||
|
return metav1.Status{
|
||||||
|
Status: metav1.StatusFailure,
|
||||||
|
Code: http.StatusUnsupportedMediaType,
|
||||||
|
Reason: metav1.StatusReason("UnsupportedMediaType"),
|
||||||
|
Message: e.Error(),
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package negotiation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mime"
|
"mime"
|
||||||
@ -28,8 +28,8 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mediaTypesForSerializer returns a list of media and stream media types for the server.
|
// MediaTypesForSerializer returns a list of media and stream media types for the server.
|
||||||
func mediaTypesForSerializer(ns runtime.NegotiatedSerializer) (mediaTypes, streamMediaTypes []string) {
|
func MediaTypesForSerializer(ns runtime.NegotiatedSerializer) (mediaTypes, streamMediaTypes []string) {
|
||||||
for _, info := range ns.SupportedMediaTypes() {
|
for _, info := range ns.SupportedMediaTypes() {
|
||||||
mediaTypes = append(mediaTypes, info.MediaType)
|
mediaTypes = append(mediaTypes, info.MediaType)
|
||||||
if info.StreamSerializer != nil {
|
if info.StreamSerializer != nil {
|
||||||
@ -40,10 +40,10 @@ func mediaTypesForSerializer(ns runtime.NegotiatedSerializer) (mediaTypes, strea
|
|||||||
return mediaTypes, streamMediaTypes
|
return mediaTypes, streamMediaTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
func negotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
func NegotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
||||||
mediaType, ok := negotiateMediaTypeOptions(req.Header.Get("Accept"), acceptedMediaTypesForEndpoint(ns), defaultEndpointRestrictions)
|
mediaType, ok := negotiateMediaTypeOptions(req.Header.Get("Accept"), acceptedMediaTypesForEndpoint(ns), defaultEndpointRestrictions)
|
||||||
if !ok {
|
if !ok {
|
||||||
supported, _ := mediaTypesForSerializer(ns)
|
supported, _ := MediaTypesForSerializer(ns)
|
||||||
return runtime.SerializerInfo{}, errNotAcceptable{supported}
|
return runtime.SerializerInfo{}, errNotAcceptable{supported}
|
||||||
}
|
}
|
||||||
// TODO: move into resthandler
|
// TODO: move into resthandler
|
||||||
@ -54,16 +54,16 @@ func negotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerialize
|
|||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func negotiateOutputStreamSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
func NegotiateOutputStreamSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
||||||
mediaType, ok := negotiateMediaTypeOptions(req.Header.Get("Accept"), acceptedMediaTypesForEndpoint(ns), defaultEndpointRestrictions)
|
mediaType, ok := negotiateMediaTypeOptions(req.Header.Get("Accept"), acceptedMediaTypesForEndpoint(ns), defaultEndpointRestrictions)
|
||||||
if !ok || mediaType.accepted.Serializer.StreamSerializer == nil {
|
if !ok || mediaType.accepted.Serializer.StreamSerializer == nil {
|
||||||
_, supported := mediaTypesForSerializer(ns)
|
_, supported := MediaTypesForSerializer(ns)
|
||||||
return runtime.SerializerInfo{}, errNotAcceptable{supported}
|
return runtime.SerializerInfo{}, errNotAcceptable{supported}
|
||||||
}
|
}
|
||||||
return mediaType.accepted.Serializer, nil
|
return mediaType.accepted.Serializer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func negotiateInputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
func NegotiateInputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
||||||
mediaTypes := ns.SupportedMediaTypes()
|
mediaTypes := ns.SupportedMediaTypes()
|
||||||
mediaType := req.Header.Get("Content-Type")
|
mediaType := req.Header.Get("Content-Type")
|
||||||
if len(mediaType) == 0 {
|
if len(mediaType) == 0 {
|
||||||
@ -71,7 +71,7 @@ func negotiateInputSerializer(req *http.Request, ns runtime.NegotiatedSerializer
|
|||||||
}
|
}
|
||||||
mediaType, _, err := mime.ParseMediaType(mediaType)
|
mediaType, _, err := mime.ParseMediaType(mediaType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, supported := mediaTypesForSerializer(ns)
|
_, supported := MediaTypesForSerializer(ns)
|
||||||
return runtime.SerializerInfo{}, errUnsupportedMediaType{supported}
|
return runtime.SerializerInfo{}, errUnsupportedMediaType{supported}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ func negotiateInputSerializer(req *http.Request, ns runtime.NegotiatedSerializer
|
|||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, supported := mediaTypesForSerializer(ns)
|
_, supported := MediaTypesForSerializer(ns)
|
||||||
return runtime.SerializerInfo{}, errUnsupportedMediaType{supported}
|
return runtime.SerializerInfo{}, errUnsupportedMediaType{supported}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package negotiation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -25,6 +25,11 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// statusError is an object that can be converted into an metav1.Status
|
||||||
|
type statusError interface {
|
||||||
|
Status() metav1.Status
|
||||||
|
}
|
||||||
|
|
||||||
type fakeNegotiater struct {
|
type fakeNegotiater struct {
|
||||||
serializer, streamSerializer runtime.Serializer
|
serializer, streamSerializer runtime.Serializer
|
||||||
framer runtime.Framer
|
framer runtime.Framer
|
||||||
@ -207,7 +212,7 @@ func TestNegotiate(t *testing.T) {
|
|||||||
req = &http.Request{Header: http.Header{}}
|
req = &http.Request{Header: http.Header{}}
|
||||||
req.Header.Set("Accept", test.accept)
|
req.Header.Set("Accept", test.accept)
|
||||||
}
|
}
|
||||||
s, err := negotiateOutputSerializer(req, test.ns)
|
s, err := NegotiateOutputSerializer(req, test.ns)
|
||||||
switch {
|
switch {
|
||||||
case err == nil && test.errFn != nil:
|
case err == nil && test.errFn != nil:
|
||||||
t.Errorf("%d: failed: expected error", i)
|
t.Errorf("%d: failed: expected error", i)
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -30,7 +30,9 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/rest"
|
"k8s.io/kubernetes/pkg/api/rest"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/metrics"
|
"k8s.io/kubernetes/pkg/apiserver/metrics"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
"k8s.io/kubernetes/pkg/httplog"
|
"k8s.io/kubernetes/pkg/httplog"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
@ -39,16 +41,15 @@ import (
|
|||||||
proxyutil "k8s.io/kubernetes/pkg/util/proxy"
|
proxyutil "k8s.io/kubernetes/pkg/util/proxy"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProxyHandler provides a http.Handler which will proxy traffic to locations
|
// ProxyHandler provides a http.Handler which will proxy traffic to locations
|
||||||
// specified by items implementing Redirector.
|
// specified by items implementing Redirector.
|
||||||
type ProxyHandler struct {
|
type ProxyHandler struct {
|
||||||
prefix string
|
Prefix string
|
||||||
storage map[string]rest.Storage
|
Storage map[string]rest.Storage
|
||||||
serializer runtime.NegotiatedSerializer
|
Serializer runtime.NegotiatedSerializer
|
||||||
mapper api.RequestContextMapper
|
Mapper api.RequestContextMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
@ -60,21 +61,21 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
reqStart := time.Now()
|
reqStart := time.Now()
|
||||||
defer metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), w.Header().Get("Content-Type"), httpCode, reqStart)
|
defer metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), w.Header().Get("Content-Type"), httpCode, reqStart)
|
||||||
|
|
||||||
ctx, ok := r.mapper.Get(req)
|
ctx, ok := r.Mapper.Get(req)
|
||||||
if !ok {
|
if !ok {
|
||||||
internalError(w, req, errors.New("Error getting request context"))
|
responsewriters.InternalError(w, req, errors.New("Error getting request context"))
|
||||||
httpCode = http.StatusInternalServerError
|
httpCode = http.StatusInternalServerError
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
internalError(w, req, errors.New("Error getting RequestInfo from context"))
|
responsewriters.InternalError(w, req, errors.New("Error getting RequestInfo from context"))
|
||||||
httpCode = http.StatusInternalServerError
|
httpCode = http.StatusInternalServerError
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !requestInfo.IsResourceRequest {
|
if !requestInfo.IsResourceRequest {
|
||||||
notFound(w, req)
|
responsewriters.NotFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
httpCode = http.StatusNotFound
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -83,7 +84,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
notFound(w, req)
|
responsewriters.NotFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
httpCode = http.StatusNotFound
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -99,10 +100,10 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
remainder = remainder + "/"
|
remainder = remainder + "/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
storage, ok := r.storage[resource]
|
storage, ok := r.Storage[resource]
|
||||||
if !ok {
|
if !ok {
|
||||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
|
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
|
||||||
notFound(w, req)
|
responsewriters.NotFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
httpCode = http.StatusNotFound
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -113,19 +114,19 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
redirector, ok := storage.(rest.Redirector)
|
redirector, ok := storage.(rest.Redirector)
|
||||||
if !ok {
|
if !ok {
|
||||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
|
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
|
||||||
httpCode = errorNegotiated(apierrors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.serializer, gv, w, req)
|
httpCode = responsewriters.ErrorNegotiated(apierrors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.Serializer, gv, w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
location, roundTripper, err := redirector.ResourceLocation(ctx, id)
|
location, roundTripper, err := redirector.ResourceLocation(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err)
|
httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err)
|
||||||
httpCode = errorNegotiated(err, r.serializer, gv, w, req)
|
httpCode = responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if location == nil {
|
if location == nil {
|
||||||
httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id)
|
httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id)
|
||||||
notFound(w, req)
|
responsewriters.NotFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
httpCode = http.StatusNotFound
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -153,7 +154,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
newReq, err := http.NewRequest(req.Method, location.String(), req.Body)
|
newReq, err := http.NewRequest(req.Method, location.String(), req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpCode = errorNegotiated(err, r.serializer, gv, w, req)
|
httpCode = responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpCode = http.StatusOK
|
httpCode = http.StatusOK
|
||||||
@ -197,9 +198,9 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
if !alreadyRewriting {
|
if !alreadyRewriting {
|
||||||
glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL)
|
glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL)
|
||||||
prepend := path.Join(r.prefix, resource, id)
|
prepend := path.Join(r.Prefix, resource, id)
|
||||||
if len(namespace) > 0 {
|
if len(namespace) > 0 {
|
||||||
prepend = path.Join(r.prefix, "namespaces", namespace, resource, id)
|
prepend = path.Join(r.Prefix, "namespaces", namespace, resource, id)
|
||||||
}
|
}
|
||||||
pTransport := &proxyutil.Transport{
|
pTransport := &proxyutil.Transport{
|
||||||
Scheme: req.URL.Scheme,
|
Scheme: req.URL.Scheme,
|
||||||
@ -221,7 +222,7 @@ func (r *ProxyHandler) tryUpgrade(w http.ResponseWriter, req, newReq *http.Reque
|
|||||||
}
|
}
|
||||||
backendConn, err := proxyutil.DialURL(location, transport)
|
backendConn, err := proxyutil.DialURL(location, transport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorNegotiated(err, r.serializer, gv, w, req)
|
responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
defer backendConn.Close()
|
defer backendConn.Close()
|
||||||
@ -231,13 +232,13 @@ func (r *ProxyHandler) tryUpgrade(w http.ResponseWriter, req, newReq *http.Reque
|
|||||||
// hijack, just for reference...
|
// hijack, just for reference...
|
||||||
requestHijackedConn, _, err := w.(http.Hijacker).Hijack()
|
requestHijackedConn, _, err := w.(http.Hijacker).Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorNegotiated(err, r.serializer, gv, w, req)
|
responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
defer requestHijackedConn.Close()
|
defer requestHijackedConn.Close()
|
||||||
|
|
||||||
if err = newReq.Write(backendConn); err != nil {
|
if err = newReq.Write(backendConn); err != nil {
|
||||||
errorNegotiated(err, r.serializer, gv, w, req)
|
responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
27
pkg/apiserver/handlers/responsewriters/BUILD
Normal file
27
pkg/apiserver/handlers/responsewriters/BUILD
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"writers.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api/rest:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/errors:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/negotiation:go_default_library",
|
||||||
|
"//pkg/runtime:go_default_library",
|
||||||
|
"//pkg/runtime/schema:go_default_library",
|
||||||
|
"//pkg/util/flushwriter:go_default_library",
|
||||||
|
"//pkg/util/runtime:go_default_library",
|
||||||
|
"//pkg/util/wsstream:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
18
pkg/apiserver/handlers/responsewriters/doc.go
Normal file
18
pkg/apiserver/handlers/responsewriters/doc.go
Normal 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 responsewriters containers helpers to write responses in HTTP handlers.
|
||||||
|
package responsewriters // import "k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
152
pkg/apiserver/handlers/responsewriters/writers.go
Normal file
152
pkg/apiserver/handlers/responsewriters/writers.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
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 responsewriters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/rest"
|
||||||
|
handlererrors "k8s.io/kubernetes/pkg/apiserver/handlers/errors"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/negotiation"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
|
"k8s.io/kubernetes/pkg/util/flushwriter"
|
||||||
|
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/util/wsstream"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteObject renders a returned runtime.Object to the response as a stream or an encoded object. If the object
|
||||||
|
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
|
||||||
|
// response. The Accept header and current API version will be passed in, and the output will be copied
|
||||||
|
// directly to the response body. If content type is returned it is used, otherwise the content type will
|
||||||
|
// be "application/octet-stream". All other objects are sent to standard JSON serialization.
|
||||||
|
func WriteObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
||||||
|
stream, ok := object.(rest.ResourceStreamer)
|
||||||
|
if !ok {
|
||||||
|
WriteObjectNegotiated(s, gv, w, req, statusCode, object)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept"))
|
||||||
|
if err != nil {
|
||||||
|
ErrorNegotiated(err, s, gv, w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out == nil {
|
||||||
|
// No output provided - return StatusNoContent
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
if wsstream.IsWebSocketRequest(req) {
|
||||||
|
r := wsstream.NewReader(out, true, wsstream.NewDefaultReaderProtocols())
|
||||||
|
if err := r.Copy(w, req); err != nil {
|
||||||
|
utilruntime.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contentType) == 0 {
|
||||||
|
contentType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
writer := w.(io.Writer)
|
||||||
|
if flush {
|
||||||
|
writer = flushwriter.Wrap(w)
|
||||||
|
}
|
||||||
|
io.Copy(writer, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteObjectNegotiated renders an object in the content type negotiated by the client
|
||||||
|
func WriteObjectNegotiated(s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||||
|
serializer, err := negotiation.NegotiateOutputSerializer(req, s)
|
||||||
|
if err != nil {
|
||||||
|
status := handlererrors.ErrToAPIStatus(err)
|
||||||
|
WriteRawJSON(int(status.Code), status, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", serializer.MediaType)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
|
||||||
|
encoder := s.EncoderForVersion(serializer.Serializer, gv)
|
||||||
|
if err := encoder.Encode(object, w); err != nil {
|
||||||
|
errorJSONFatal(err, encoder, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorNegotiated renders an error to the response. Returns the HTTP status code of the error.
|
||||||
|
func ErrorNegotiated(err error, s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request) int {
|
||||||
|
status := handlererrors.ErrToAPIStatus(err)
|
||||||
|
code := int(status.Code)
|
||||||
|
// when writing an error, check to see if the status indicates a retry after period
|
||||||
|
if status.Details != nil && status.Details.RetryAfterSeconds > 0 {
|
||||||
|
delay := strconv.Itoa(int(status.Details.RetryAfterSeconds))
|
||||||
|
w.Header().Set("Retry-After", delay)
|
||||||
|
}
|
||||||
|
WriteObjectNegotiated(s, gv, w, req, code, status)
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorJSONFatal renders an error to the response, and if codec fails will render plaintext.
|
||||||
|
// Returns the HTTP status code of the error.
|
||||||
|
func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int {
|
||||||
|
utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
|
||||||
|
status := handlererrors.ErrToAPIStatus(err)
|
||||||
|
code := int(status.Code)
|
||||||
|
output, err := runtime.Encode(codec, status)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(code)
|
||||||
|
fmt.Fprintf(w, "%s: %s", status.Reason, status.Message)
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(code)
|
||||||
|
w.Write(output)
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
w.Write(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFound renders a simple not found error.
|
||||||
|
func NotFound(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintf(w, "Not Found: %#v", req.RequestURI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalError renders a simple internal error
|
||||||
|
func InternalError(w http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "Internal Server Error: %#v", req.RequestURI)
|
||||||
|
utilruntime.HandleError(err)
|
||||||
|
}
|
@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -32,6 +33,8 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api/meta"
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
"k8s.io/kubernetes/pkg/api/rest"
|
"k8s.io/kubernetes/pkg/api/rest"
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/negotiation"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
"k8s.io/kubernetes/pkg/fields"
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
@ -86,7 +89,7 @@ type RequestScope struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Request) {
|
func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Request) {
|
||||||
errorNegotiated(err, scope.Serializer, scope.Kind.GroupVersion(), w, req)
|
responsewriters.ErrorNegotiated(err, scope.Serializer, scope.Kind.GroupVersion(), w, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getterFunc performs a get request with the given context and object name. The request
|
// getterFunc performs a get request with the given context and object name. The request
|
||||||
@ -118,7 +121,7 @@ func getResourceHandler(scope RequestScope, getter getterFunc) restful.RouteFunc
|
|||||||
scope.err(err, res.ResponseWriter, req.Request)
|
scope.err(err, res.ResponseWriter, req.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +231,7 @@ type responder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *responder) Object(statusCode int, obj runtime.Object) {
|
func (r *responder) Object(statusCode int, obj runtime.Object) {
|
||||||
write(statusCode, r.scope.Kind.GroupVersion(), r.scope.Serializer, obj, r.res.ResponseWriter, r.req.Request)
|
responsewriters.WriteObject(statusCode, r.scope.Kind.GroupVersion(), r.scope.Serializer, obj, r.res.ResponseWriter, r.req.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responder) Error(err error) {
|
func (r *responder) Error(err error) {
|
||||||
@ -330,7 +333,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
trace.Step("Self-linking done")
|
trace.Step("Self-linking done")
|
||||||
write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
||||||
trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems))
|
trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -364,7 +367,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
|
|||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
|
||||||
gv := scope.Kind.GroupVersion()
|
gv := scope.Kind.GroupVersion()
|
||||||
s, err := negotiateInputSerializer(req.Request, scope.Serializer)
|
s, err := negotiation.NegotiateInputSerializer(req.Request, scope.Serializer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scope.err(err, res.ResponseWriter, req.Request)
|
scope.err(err, res.ResponseWriter, req.Request)
|
||||||
return
|
return
|
||||||
@ -423,7 +426,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
|
|||||||
}
|
}
|
||||||
trace.Step("Self-link added")
|
trace.Step("Self-link added")
|
||||||
|
|
||||||
write(http.StatusCreated, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
responsewriters.WriteObject(http.StatusCreated, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,7 +519,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -682,7 +685,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := negotiateInputSerializer(req.Request, scope.Serializer)
|
s, err := negotiation.NegotiateInputSerializer(req.Request, scope.Serializer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scope.err(err, res.ResponseWriter, req.Request)
|
scope.err(err, res.ResponseWriter, req.Request)
|
||||||
return
|
return
|
||||||
@ -739,7 +742,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType
|
|||||||
if wasCreated {
|
if wasCreated {
|
||||||
status = http.StatusCreated
|
status = http.StatusCreated
|
||||||
}
|
}
|
||||||
write(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
responsewriters.WriteObject(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -771,7 +774,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(body) > 0 {
|
if len(body) > 0 {
|
||||||
s, err := negotiateInputSerializer(req.Request, scope.Serializer)
|
s, err := negotiation.NegotiateInputSerializer(req.Request, scope.Serializer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scope.err(err, res.ResponseWriter, req.Request)
|
scope.err(err, res.ResponseWriter, req.Request)
|
||||||
return
|
return
|
||||||
@ -836,7 +839,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
responsewriters.WriteObject(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,7 +898,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(body) > 0 {
|
if len(body) > 0 {
|
||||||
s, err := negotiateInputSerializer(req.Request, scope.Serializer)
|
s, err := negotiation.NegotiateInputSerializer(req.Request, scope.Serializer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scope.err(err, res.ResponseWriter, req.Request)
|
scope.err(err, res.ResponseWriter, req.Request)
|
||||||
return
|
return
|
||||||
@ -940,7 +943,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeNegotiated(scope.Serializer, scope.Kind.GroupVersion(), w, req.Request, http.StatusOK, result)
|
responsewriters.WriteObjectNegotiated(scope.Serializer, scope.Kind.GroupVersion(), w, req.Request, http.StatusOK, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1101,3 +1104,19 @@ func summarizeData(data []byte, maxLength int) string {
|
|||||||
return hex.EncodeToString(data)
|
return hex.EncodeToString(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readBody(req *http.Request) ([]byte, error) {
|
||||||
|
defer req.Body.Close()
|
||||||
|
return ioutil.ReadAll(req.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTimeout(str string) time.Duration {
|
||||||
|
if str != "" {
|
||||||
|
timeout, err := time.ParseDuration(str)
|
||||||
|
if err == nil {
|
||||||
|
return timeout
|
||||||
|
}
|
||||||
|
glog.Errorf("Failed to parse %q: %v", str, err)
|
||||||
|
}
|
||||||
|
return 30 * time.Second
|
||||||
|
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -472,3 +472,15 @@ func TestHasUID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseTimeout(t *testing.T) {
|
||||||
|
if d := parseTimeout(""); d != 30*time.Second {
|
||||||
|
t.Errorf("blank timeout produces %v", d)
|
||||||
|
}
|
||||||
|
if d := parseTimeout("not a timeout"); d != 30*time.Second {
|
||||||
|
t.Errorf("bad timeout produces %v", d)
|
||||||
|
}
|
||||||
|
if d := parseTimeout("10s"); d != 10*time.Second {
|
||||||
|
t.Errorf("10s timeout produced: %v", d)
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -24,6 +24,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/negotiation"
|
||||||
"k8s.io/kubernetes/pkg/httplog"
|
"k8s.io/kubernetes/pkg/httplog"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
"k8s.io/kubernetes/pkg/runtime/serializer/streaming"
|
"k8s.io/kubernetes/pkg/runtime/serializer/streaming"
|
||||||
@ -40,7 +41,7 @@ import (
|
|||||||
var neverExitWatch <-chan time.Time = make(chan time.Time)
|
var neverExitWatch <-chan time.Time = make(chan time.Time)
|
||||||
|
|
||||||
// timeoutFactory abstracts watch timeout logic for testing
|
// timeoutFactory abstracts watch timeout logic for testing
|
||||||
type timeoutFactory interface {
|
type TimeoutFactory interface {
|
||||||
TimeoutCh() (<-chan time.Time, func() bool)
|
TimeoutCh() (<-chan time.Time, func() bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) {
|
|||||||
// TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled.
|
// TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled.
|
||||||
func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Request, res *restful.Response, timeout time.Duration) {
|
func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Request, res *restful.Response, timeout time.Duration) {
|
||||||
// negotiate for the stream serializer
|
// negotiate for the stream serializer
|
||||||
serializer, err := negotiateOutputStreamSerializer(req.Request, scope.Serializer)
|
serializer, err := negotiation.NegotiateOutputStreamSerializer(req.Request, scope.Serializer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scope.err(err, res.ResponseWriter, req.Request)
|
scope.err(err, res.ResponseWriter, req.Request)
|
||||||
return
|
return
|
||||||
@ -89,21 +90,21 @@ func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
server := &WatchServer{
|
server := &WatchServer{
|
||||||
watching: watcher,
|
Watching: watcher,
|
||||||
scope: scope,
|
Scope: scope,
|
||||||
|
|
||||||
useTextFraming: useTextFraming,
|
UseTextFraming: useTextFraming,
|
||||||
mediaType: mediaType,
|
MediaType: mediaType,
|
||||||
framer: framer,
|
Framer: framer,
|
||||||
encoder: encoder,
|
Encoder: encoder,
|
||||||
embeddedEncoder: embeddedEncoder,
|
EmbeddedEncoder: embeddedEncoder,
|
||||||
fixup: func(obj runtime.Object) {
|
Fixup: func(obj runtime.Object) {
|
||||||
if err := setSelfLink(obj, req, scope.Namer); err != nil {
|
if err := setSelfLink(obj, req, scope.Namer); err != nil {
|
||||||
utilruntime.HandleError(fmt.Errorf("failed to set link for object %v: %v", reflect.TypeOf(obj), err))
|
utilruntime.HandleError(fmt.Errorf("failed to set link for object %v: %v", reflect.TypeOf(obj), err))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
t: &realTimeoutFactory{timeout},
|
TimeoutFactory: &realTimeoutFactory{timeout},
|
||||||
}
|
}
|
||||||
|
|
||||||
server.ServeHTTP(res.ResponseWriter, req.Request)
|
server.ServeHTTP(res.ResponseWriter, req.Request)
|
||||||
@ -111,22 +112,22 @@ func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Reques
|
|||||||
|
|
||||||
// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.
|
// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.
|
||||||
type WatchServer struct {
|
type WatchServer struct {
|
||||||
watching watch.Interface
|
Watching watch.Interface
|
||||||
scope RequestScope
|
Scope RequestScope
|
||||||
|
|
||||||
// true if websocket messages should use text framing (as opposed to binary framing)
|
// true if websocket messages should use text framing (as opposed to binary framing)
|
||||||
useTextFraming bool
|
UseTextFraming bool
|
||||||
// the media type this watch is being served with
|
// the media type this watch is being served with
|
||||||
mediaType string
|
MediaType string
|
||||||
// used to frame the watch stream
|
// used to frame the watch stream
|
||||||
framer runtime.Framer
|
Framer runtime.Framer
|
||||||
// used to encode the watch stream event itself
|
// used to encode the watch stream event itself
|
||||||
encoder runtime.Encoder
|
Encoder runtime.Encoder
|
||||||
// used to encode the nested object in the watch stream
|
// used to encode the nested object in the watch stream
|
||||||
embeddedEncoder runtime.Encoder
|
EmbeddedEncoder runtime.Encoder
|
||||||
fixup func(runtime.Object)
|
Fixup func(runtime.Object)
|
||||||
|
|
||||||
t timeoutFactory
|
TimeoutFactory TimeoutFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP serves a series of encoded events via HTTP with Transfer-Encoding: chunked
|
// ServeHTTP serves a series of encoded events via HTTP with Transfer-Encoding: chunked
|
||||||
@ -135,7 +136,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
w = httplog.Unlogged(w)
|
w = httplog.Unlogged(w)
|
||||||
|
|
||||||
if wsstream.IsWebSocketRequest(req) {
|
if wsstream.IsWebSocketRequest(req) {
|
||||||
w.Header().Set("Content-Type", s.mediaType)
|
w.Header().Set("Content-Type", s.MediaType)
|
||||||
websocket.Handler(s.HandleWS).ServeHTTP(w, req)
|
websocket.Handler(s.HandleWS).ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -144,34 +145,34 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
err := fmt.Errorf("unable to start watch - can't get http.CloseNotifier: %#v", w)
|
err := fmt.Errorf("unable to start watch - can't get http.CloseNotifier: %#v", w)
|
||||||
utilruntime.HandleError(err)
|
utilruntime.HandleError(err)
|
||||||
s.scope.err(errors.NewInternalError(err), w, req)
|
s.Scope.err(errors.NewInternalError(err), w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
flusher, ok := w.(http.Flusher)
|
flusher, ok := w.(http.Flusher)
|
||||||
if !ok {
|
if !ok {
|
||||||
err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w)
|
err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w)
|
||||||
utilruntime.HandleError(err)
|
utilruntime.HandleError(err)
|
||||||
s.scope.err(errors.NewInternalError(err), w, req)
|
s.Scope.err(errors.NewInternalError(err), w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
framer := s.framer.NewFrameWriter(w)
|
framer := s.Framer.NewFrameWriter(w)
|
||||||
if framer == nil {
|
if framer == nil {
|
||||||
// programmer error
|
// programmer error
|
||||||
err := fmt.Errorf("no stream framing support is available for media type %q", s.mediaType)
|
err := fmt.Errorf("no stream framing support is available for media type %q", s.MediaType)
|
||||||
utilruntime.HandleError(err)
|
utilruntime.HandleError(err)
|
||||||
s.scope.err(errors.NewBadRequest(err.Error()), w, req)
|
s.Scope.err(errors.NewBadRequest(err.Error()), w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e := streaming.NewEncoder(framer, s.encoder)
|
e := streaming.NewEncoder(framer, s.Encoder)
|
||||||
|
|
||||||
// ensure the connection times out
|
// ensure the connection times out
|
||||||
timeoutCh, cleanup := s.t.TimeoutCh()
|
timeoutCh, cleanup := s.TimeoutFactory.TimeoutCh()
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
defer s.watching.Stop()
|
defer s.Watching.Stop()
|
||||||
|
|
||||||
// begin the stream
|
// begin the stream
|
||||||
w.Header().Set("Content-Type", s.mediaType)
|
w.Header().Set("Content-Type", s.MediaType)
|
||||||
w.Header().Set("Transfer-Encoding", "chunked")
|
w.Header().Set("Transfer-Encoding", "chunked")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
@ -179,7 +180,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
var unknown runtime.Unknown
|
var unknown runtime.Unknown
|
||||||
internalEvent := &versioned.InternalEvent{}
|
internalEvent := &versioned.InternalEvent{}
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
ch := s.watching.ResultChan()
|
ch := s.Watching.ResultChan()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-cn.CloseNotify():
|
case <-cn.CloseNotify():
|
||||||
@ -193,8 +194,8 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
obj := event.Object
|
obj := event.Object
|
||||||
s.fixup(obj)
|
s.Fixup(obj)
|
||||||
if err := s.embeddedEncoder.Encode(obj, buf); err != nil {
|
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||||
// unexpected error
|
// unexpected error
|
||||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
|
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
|
||||||
return
|
return
|
||||||
@ -239,11 +240,11 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) {
|
|||||||
internalEvent := &versioned.InternalEvent{}
|
internalEvent := &versioned.InternalEvent{}
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
streamBuf := &bytes.Buffer{}
|
streamBuf := &bytes.Buffer{}
|
||||||
ch := s.watching.ResultChan()
|
ch := s.Watching.ResultChan()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
s.watching.Stop()
|
s.Watching.Stop()
|
||||||
return
|
return
|
||||||
case event, ok := <-ch:
|
case event, ok := <-ch:
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -251,8 +252,8 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
obj := event.Object
|
obj := event.Object
|
||||||
s.fixup(obj)
|
s.Fixup(obj)
|
||||||
if err := s.embeddedEncoder.Encode(obj, buf); err != nil {
|
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||||
// unexpected error
|
// unexpected error
|
||||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
|
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
|
||||||
return
|
return
|
||||||
@ -265,22 +266,22 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) {
|
|||||||
|
|
||||||
// the internal event will be versioned by the encoder
|
// the internal event will be versioned by the encoder
|
||||||
*internalEvent = versioned.InternalEvent(event)
|
*internalEvent = versioned.InternalEvent(event)
|
||||||
if err := s.encoder.Encode(internalEvent, streamBuf); err != nil {
|
if err := s.Encoder.Encode(internalEvent, streamBuf); err != nil {
|
||||||
// encoding error
|
// encoding error
|
||||||
utilruntime.HandleError(fmt.Errorf("unable to encode event: %v", err))
|
utilruntime.HandleError(fmt.Errorf("unable to encode event: %v", err))
|
||||||
s.watching.Stop()
|
s.Watching.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s.useTextFraming {
|
if s.UseTextFraming {
|
||||||
if err := websocket.Message.Send(ws, streamBuf.String()); err != nil {
|
if err := websocket.Message.Send(ws, streamBuf.String()); err != nil {
|
||||||
// Client disconnect.
|
// Client disconnect.
|
||||||
s.watching.Stop()
|
s.Watching.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := websocket.Message.Send(ws, streamBuf.Bytes()); err != nil {
|
if err := websocket.Message.Send(ws, streamBuf.Bytes()); err != nil {
|
||||||
// Client disconnect.
|
// Client disconnect.
|
||||||
s.watching.Stop()
|
s.Watching.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -33,6 +33,8 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api/rest"
|
"k8s.io/kubernetes/pkg/api/rest"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/negotiation"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/metrics"
|
"k8s.io/kubernetes/pkg/apiserver/metrics"
|
||||||
"k8s.io/kubernetes/pkg/conversion"
|
"k8s.io/kubernetes/pkg/conversion"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
@ -53,7 +55,7 @@ type action struct {
|
|||||||
Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc).
|
Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc).
|
||||||
Path string // The path of the action
|
Path string // The path of the action
|
||||||
Params []*restful.Parameter // List of parameters associated with the action.
|
Params []*restful.Parameter // List of parameters associated with the action.
|
||||||
Namer ScopeNamer
|
Namer handlers.ScopeNamer
|
||||||
AllNamespaces bool // true iff the action is namespaced but works on aggregate result for all namespaces
|
AllNamespaces bool // true iff the action is namespaced but works on aggregate result for all namespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,11 +86,11 @@ var errEmptyName = errors.NewBadRequest("name must be provided")
|
|||||||
func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []metav1.APIResource, errors []error) {
|
func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []metav1.APIResource, errors []error) {
|
||||||
errors = make([]error, 0)
|
errors = make([]error, 0)
|
||||||
|
|
||||||
proxyHandler := (&ProxyHandler{
|
proxyHandler := (&handlers.ProxyHandler{
|
||||||
prefix: a.prefix + "/proxy/",
|
Prefix: a.prefix + "/proxy/",
|
||||||
storage: a.group.Storage,
|
Storage: a.group.Storage,
|
||||||
serializer: a.group.Serializer,
|
Serializer: a.group.Serializer,
|
||||||
mapper: a.group.Context,
|
Mapper: a.group.Context,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
|
// Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
|
||||||
@ -121,7 +123,7 @@ func (a *APIInstaller) NewWebService() *restful.WebService {
|
|||||||
// If we stop using go-restful, we can default empty content-type to application/json on an
|
// If we stop using go-restful, we can default empty content-type to application/json on an
|
||||||
// endpoint by endpoint basis
|
// endpoint by endpoint basis
|
||||||
ws.Consumes("*/*")
|
ws.Consumes("*/*")
|
||||||
mediaTypes, streamMediaTypes := mediaTypesForSerializer(a.group.Serializer)
|
mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
|
||||||
ws.Produces(append(mediaTypes, streamMediaTypes...)...)
|
ws.Produces(append(mediaTypes, streamMediaTypes...)...)
|
||||||
ws.ApiVersion(a.group.GroupVersion.String())
|
ws.ApiVersion(a.group.GroupVersion.String())
|
||||||
|
|
||||||
@ -338,7 +340,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctxFn ContextFunc
|
var ctxFn handlers.ContextFunc
|
||||||
ctxFn = func(req *restful.Request) api.Context {
|
ctxFn = func(req *restful.Request) api.Context {
|
||||||
if context == nil {
|
if context == nil {
|
||||||
return api.WithUserAgent(api.NewContext(), req.HeaderParameter("User-Agent"))
|
return api.WithUserAgent(api.NewContext(), req.HeaderParameter("User-Agent"))
|
||||||
@ -501,12 +503,12 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
//
|
//
|
||||||
// test/integration/auth_test.go is currently the most comprehensive status code test
|
// test/integration/auth_test.go is currently the most comprehensive status code test
|
||||||
|
|
||||||
mediaTypes, streamMediaTypes := mediaTypesForSerializer(a.group.Serializer)
|
mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
|
||||||
allMediaTypes := append(mediaTypes, streamMediaTypes...)
|
allMediaTypes := append(mediaTypes, streamMediaTypes...)
|
||||||
ws.Produces(allMediaTypes...)
|
ws.Produces(allMediaTypes...)
|
||||||
|
|
||||||
kubeVerbs := map[string]struct{}{}
|
kubeVerbs := map[string]struct{}{}
|
||||||
reqScope := RequestScope{
|
reqScope := handlers.RequestScope{
|
||||||
ContextFunc: ctxFn,
|
ContextFunc: ctxFn,
|
||||||
Serializer: a.group.Serializer,
|
Serializer: a.group.Serializer,
|
||||||
ParameterCodec: a.group.ParameterCodec,
|
ParameterCodec: a.group.ParameterCodec,
|
||||||
@ -550,9 +552,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
case "GET": // Get a resource.
|
case "GET": // Get a resource.
|
||||||
var handler restful.RouteFunction
|
var handler restful.RouteFunction
|
||||||
if isGetterWithOptions {
|
if isGetterWithOptions {
|
||||||
handler = GetResourceWithOptions(getterWithOptions, reqScope)
|
handler = handlers.GetResourceWithOptions(getterWithOptions, reqScope)
|
||||||
} else {
|
} else {
|
||||||
handler = GetResource(getter, exporter, reqScope)
|
handler = handlers.GetResource(getter, exporter, reqScope)
|
||||||
}
|
}
|
||||||
handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler)
|
handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler)
|
||||||
doc := "read the specified " + kind
|
doc := "read the specified " + kind
|
||||||
@ -583,7 +585,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
if hasSubresource {
|
if hasSubresource {
|
||||||
doc = "list " + subresource + " of objects of kind " + kind
|
doc = "list " + subresource + " of objects of kind " + kind
|
||||||
}
|
}
|
||||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
|
handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.ListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
|
||||||
route := ws.GET(action.Path).To(handler).
|
route := ws.GET(action.Path).To(handler).
|
||||||
Doc(doc).
|
Doc(doc).
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||||
@ -615,7 +617,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
if hasSubresource {
|
if hasSubresource {
|
||||||
doc = "replace " + subresource + " of the specified " + kind
|
doc = "replace " + subresource + " of the specified " + kind
|
||||||
}
|
}
|
||||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, UpdateResource(updater, reqScope, a.group.Typer, admit))
|
handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.UpdateResource(updater, reqScope, a.group.Typer, admit))
|
||||||
route := ws.PUT(action.Path).To(handler).
|
route := ws.PUT(action.Path).To(handler).
|
||||||
Doc(doc).
|
Doc(doc).
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||||
@ -631,7 +633,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
if hasSubresource {
|
if hasSubresource {
|
||||||
doc = "partially update " + subresource + " of the specified " + kind
|
doc = "partially update " + subresource + " of the specified " + kind
|
||||||
}
|
}
|
||||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, PatchResource(patcher, reqScope, a.group.Typer, admit, mapping.ObjectConvertor))
|
handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.PatchResource(patcher, reqScope, a.group.Typer, admit, mapping.ObjectConvertor))
|
||||||
route := ws.PATCH(action.Path).To(handler).
|
route := ws.PATCH(action.Path).To(handler).
|
||||||
Doc(doc).
|
Doc(doc).
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||||
@ -646,9 +648,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
case "POST": // Create a resource.
|
case "POST": // Create a resource.
|
||||||
var handler restful.RouteFunction
|
var handler restful.RouteFunction
|
||||||
if isNamedCreater {
|
if isNamedCreater {
|
||||||
handler = CreateNamedResource(namedCreater, reqScope, a.group.Typer, admit)
|
handler = handlers.CreateNamedResource(namedCreater, reqScope, a.group.Typer, admit)
|
||||||
} else {
|
} else {
|
||||||
handler = CreateResource(creater, reqScope, a.group.Typer, admit)
|
handler = handlers.CreateResource(creater, reqScope, a.group.Typer, admit)
|
||||||
}
|
}
|
||||||
handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler)
|
handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler)
|
||||||
article := utilstrings.GetArticleForNoun(kind, " ")
|
article := utilstrings.GetArticleForNoun(kind, " ")
|
||||||
@ -672,7 +674,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
if hasSubresource {
|
if hasSubresource {
|
||||||
doc = "delete " + subresource + " of" + article + kind
|
doc = "delete " + subresource + " of" + article + kind
|
||||||
}
|
}
|
||||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, DeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
|
handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.DeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
|
||||||
route := ws.DELETE(action.Path).To(handler).
|
route := ws.DELETE(action.Path).To(handler).
|
||||||
Doc(doc).
|
Doc(doc).
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||||
@ -693,7 +695,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
if hasSubresource {
|
if hasSubresource {
|
||||||
doc = "delete collection of " + subresource + " of a " + kind
|
doc = "delete collection of " + subresource + " of a " + kind
|
||||||
}
|
}
|
||||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, DeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
|
handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.DeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
|
||||||
route := ws.DELETE(action.Path).To(handler).
|
route := ws.DELETE(action.Path).To(handler).
|
||||||
Doc(doc).
|
Doc(doc).
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||||
@ -712,7 +714,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
if hasSubresource {
|
if hasSubresource {
|
||||||
doc = "watch changes to " + subresource + " of an object of kind " + kind
|
doc = "watch changes to " + subresource + " of an object of kind " + kind
|
||||||
}
|
}
|
||||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.ListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
||||||
route := ws.GET(action.Path).To(handler).
|
route := ws.GET(action.Path).To(handler).
|
||||||
Doc(doc).
|
Doc(doc).
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||||
@ -731,7 +733,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
if hasSubresource {
|
if hasSubresource {
|
||||||
doc = "watch individual changes to a list of " + subresource + " of " + kind
|
doc = "watch individual changes to a list of " + subresource + " of " + kind
|
||||||
}
|
}
|
||||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.ListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
|
||||||
route := ws.GET(action.Path).To(handler).
|
route := ws.GET(action.Path).To(handler).
|
||||||
Doc(doc).
|
Doc(doc).
|
||||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||||
@ -761,7 +763,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
if hasSubresource {
|
if hasSubresource {
|
||||||
doc = "connect " + method + " requests to " + subresource + " of " + kind
|
doc = "connect " + method + " requests to " + subresource + " of " + kind
|
||||||
}
|
}
|
||||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, ConnectResource(connecter, reqScope, admit, path))
|
handler := metrics.InstrumentRouteFunc(action.Verb, resource, handlers.ConnectResource(connecter, reqScope, admit, path))
|
||||||
route := ws.Method(method).Path(action.Path).
|
route := ws.Method(method).Path(action.Path).
|
||||||
To(handler).
|
To(handler).
|
||||||
Doc(doc).
|
Doc(doc).
|
||||||
@ -802,7 +804,7 @@ type rootScopeNaming struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rootScopeNaming implements ScopeNamer
|
// rootScopeNaming implements ScopeNamer
|
||||||
var _ ScopeNamer = rootScopeNaming{}
|
var _ handlers.ScopeNamer = rootScopeNaming{}
|
||||||
|
|
||||||
// Namespace returns an empty string because root scoped objects have no namespace.
|
// Namespace returns an empty string because root scoped objects have no namespace.
|
||||||
func (n rootScopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
|
func (n rootScopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
|
||||||
@ -866,7 +868,7 @@ type scopeNaming struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// scopeNaming implements ScopeNamer
|
// scopeNaming implements ScopeNamer
|
||||||
var _ ScopeNamer = scopeNaming{}
|
var _ handlers.ScopeNamer = scopeNaming{}
|
||||||
|
|
||||||
// Namespace returns the namespace from the path or the default.
|
// Namespace returns the namespace from the path or the default.
|
||||||
func (n scopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
|
func (n scopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
|
@ -1,42 +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 apiserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
|
||||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
func InstallServiceErrorHandler(s runtime.NegotiatedSerializer, container *restful.Container) {
|
|
||||||
container.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
|
||||||
serviceErrorHandler(s, serviceErr, request, response)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
|
||||||
errorNegotiated(
|
|
||||||
apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", serviceErr.Message, 0, false),
|
|
||||||
s,
|
|
||||||
schema.GroupVersion{},
|
|
||||||
response.ResponseWriter,
|
|
||||||
request.Request,
|
|
||||||
)
|
|
||||||
}
|
|
@ -21,7 +21,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -33,9 +32,9 @@ import (
|
|||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/rest"
|
"k8s.io/kubernetes/pkg/api/rest"
|
||||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
|
||||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers"
|
||||||
apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing"
|
apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing"
|
||||||
"k8s.io/kubernetes/pkg/fields"
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
@ -579,16 +578,16 @@ func TestWatchHTTPTimeout(t *testing.T) {
|
|||||||
serializer := info.StreamSerializer
|
serializer := info.StreamSerializer
|
||||||
|
|
||||||
// Setup a new watchserver
|
// Setup a new watchserver
|
||||||
watchServer := &WatchServer{
|
watchServer := &handlers.WatchServer{
|
||||||
watching: watcher,
|
Watching: watcher,
|
||||||
|
|
||||||
mediaType: "testcase/json",
|
MediaType: "testcase/json",
|
||||||
framer: serializer.Framer,
|
Framer: serializer.Framer,
|
||||||
encoder: newCodec,
|
Encoder: newCodec,
|
||||||
embeddedEncoder: newCodec,
|
EmbeddedEncoder: newCodec,
|
||||||
|
|
||||||
fixup: func(obj runtime.Object) {},
|
Fixup: func(obj runtime.Object) {},
|
||||||
t: &fakeTimeoutFactory{timeoutCh, done},
|
TimeoutFactory: &fakeTimeoutFactory{timeoutCh, done},
|
||||||
}
|
}
|
||||||
|
|
||||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
@ -632,18 +631,6 @@ func TestWatchHTTPTimeout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const benchmarkSeed = 100
|
|
||||||
|
|
||||||
func benchmarkItems() []api.Pod {
|
|
||||||
apiObjectFuzzer := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed))
|
|
||||||
items := make([]api.Pod, 3)
|
|
||||||
for i := range items {
|
|
||||||
apiObjectFuzzer.Fuzz(&items[i])
|
|
||||||
items[i].Spec.InitContainers, items[i].Status.InitContainerStatuses = nil, nil
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkWatchHTTP measures the cost of serving a watch.
|
// BenchmarkWatchHTTP measures the cost of serving a watch.
|
||||||
func BenchmarkWatchHTTP(b *testing.B) {
|
func BenchmarkWatchHTTP(b *testing.B) {
|
||||||
items := benchmarkItems()
|
items := benchmarkItems()
|
||||||
|
@ -42,7 +42,7 @@ import (
|
|||||||
apiserverauthenticator "k8s.io/kubernetes/pkg/apiserver/authenticator"
|
apiserverauthenticator "k8s.io/kubernetes/pkg/apiserver/authenticator"
|
||||||
apiserverfilters "k8s.io/kubernetes/pkg/apiserver/filters"
|
apiserverfilters "k8s.io/kubernetes/pkg/apiserver/filters"
|
||||||
apiserveropenapi "k8s.io/kubernetes/pkg/apiserver/openapi"
|
apiserveropenapi "k8s.io/kubernetes/pkg/apiserver/openapi"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
apiserverrequest "k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||||
authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union"
|
authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union"
|
||||||
@ -627,7 +627,7 @@ func (s *GenericAPIServer) installAPI(c *Config) {
|
|||||||
s.HandlerContainer.Add(s.DynamicApisDiscovery())
|
s.HandlerContainer.Add(s.DynamicApisDiscovery())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequestInfoResolver(c *Config) *request.RequestInfoFactory {
|
func NewRequestInfoResolver(c *Config) *apiserverrequest.RequestInfoFactory {
|
||||||
apiPrefixes := sets.NewString(strings.Trim(APIGroupPrefix, "/")) // all possible API prefixes
|
apiPrefixes := sets.NewString(strings.Trim(APIGroupPrefix, "/")) // all possible API prefixes
|
||||||
legacyAPIPrefixes := sets.String{} // APIPrefixes that won't have groups (legacy)
|
legacyAPIPrefixes := sets.String{} // APIPrefixes that won't have groups (legacy)
|
||||||
for legacyAPIPrefix := range c.LegacyAPIGroupPrefixes {
|
for legacyAPIPrefix := range c.LegacyAPIGroupPrefixes {
|
||||||
@ -635,7 +635,7 @@ func NewRequestInfoResolver(c *Config) *request.RequestInfoFactory {
|
|||||||
legacyAPIPrefixes.Insert(strings.Trim(legacyAPIPrefix, "/"))
|
legacyAPIPrefixes.Insert(strings.Trim(legacyAPIPrefix, "/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &request.RequestInfoFactory{
|
return &apiserverrequest.RequestInfoFactory{
|
||||||
APIPrefixes: apiPrefixes,
|
APIPrefixes: apiPrefixes,
|
||||||
GrouplessAPIPrefixes: legacyAPIPrefixes,
|
GrouplessAPIPrefixes: legacyAPIPrefixes,
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,16 @@ package filters
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
apiserverrequest "k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LongRunningRequestCheck is a predicate which is true for long-running http requests.
|
// LongRunningRequestCheck is a predicate which is true for long-running http requests.
|
||||||
type LongRunningRequestCheck func(r *http.Request, requestInfo *request.RequestInfo) bool
|
type LongRunningRequestCheck func(r *http.Request, requestInfo *apiserverrequest.RequestInfo) bool
|
||||||
|
|
||||||
// BasicLongRunningRequestCheck returns true if the given request has one of the specified verbs or one of the specified subresources
|
// BasicLongRunningRequestCheck returns true if the given request has one of the specified verbs or one of the specified subresources
|
||||||
func BasicLongRunningRequestCheck(longRunningVerbs, longRunningSubresources sets.String) LongRunningRequestCheck {
|
func BasicLongRunningRequestCheck(longRunningVerbs, longRunningSubresources sets.String) LongRunningRequestCheck {
|
||||||
return func(r *http.Request, requestInfo *request.RequestInfo) bool {
|
return func(r *http.Request, requestInfo *apiserverrequest.RequestInfo) bool {
|
||||||
if longRunningVerbs.Has(requestInfo.Verb) {
|
if longRunningVerbs.Has(requestInfo.Verb) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
apiserverrequest "k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
"k8s.io/kubernetes/pkg/httplog"
|
"k8s.io/kubernetes/pkg/httplog"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ func WithMaxInFlightLimit(
|
|||||||
handleError(w, r, fmt.Errorf("no context found for request, handler chain must be wrong"))
|
handleError(w, r, fmt.Errorf("no context found for request, handler chain must be wrong"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
requestInfo, ok := apiserverrequest.RequestInfoFrom(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
handleError(w, r, fmt.Errorf("no RequestInfo found in context, handler chain must be wrong"))
|
handleError(w, r, fmt.Errorf("no RequestInfo found in context, handler chain must be wrong"))
|
||||||
return
|
return
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
apiserverfilters "k8s.io/kubernetes/pkg/apiserver/filters"
|
apiserverfilters "k8s.io/kubernetes/pkg/apiserver/filters"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
apiserverrequest "k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ func createMaxInflightServer(callsWg, blockWg *sync.WaitGroup, disableCallsWg *b
|
|||||||
longRunningRequestCheck := BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString("proxy"))
|
longRunningRequestCheck := BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString("proxy"))
|
||||||
|
|
||||||
requestContextMapper := api.NewRequestContextMapper()
|
requestContextMapper := api.NewRequestContextMapper()
|
||||||
requestInfoFactory := &request.RequestInfoFactory{APIPrefixes: sets.NewString("apis", "api"), GrouplessAPIPrefixes: sets.NewString("api")}
|
requestInfoFactory := &apiserverrequest.RequestInfoFactory{APIPrefixes: sets.NewString("apis", "api"), GrouplessAPIPrefixes: sets.NewString("api")}
|
||||||
handler := WithMaxInFlightLimit(
|
handler := WithMaxInFlightLimit(
|
||||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// A short, accounted request that does not wait for block WaitGroup.
|
// A short, accounted request that does not wait for block WaitGroup.
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
apiserverrequest "k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
"k8s.io/kubernetes/pkg/httplog"
|
"k8s.io/kubernetes/pkg/httplog"
|
||||||
"k8s.io/kubernetes/pkg/util/runtime"
|
"k8s.io/kubernetes/pkg/util/runtime"
|
||||||
)
|
)
|
||||||
@ -39,12 +39,12 @@ func WithPanicRecovery(handler http.Handler, requestContextMapper api.RequestCon
|
|||||||
|
|
||||||
logger := httplog.NewLogged(req, &w)
|
logger := httplog.NewLogged(req, &w)
|
||||||
|
|
||||||
var requestInfo *request.RequestInfo
|
var requestInfo *apiserverrequest.RequestInfo
|
||||||
ctx, ok := requestContextMapper.Get(req)
|
ctx, ok := requestContextMapper.Get(req)
|
||||||
if !ok {
|
if !ok {
|
||||||
glog.Errorf("no context found for request, handler chain must be wrong")
|
glog.Errorf("no context found for request, handler chain must be wrong")
|
||||||
} else {
|
} else {
|
||||||
requestInfo, ok = request.RequestInfoFrom(ctx)
|
requestInfo, ok = apiserverrequest.RequestInfoFrom(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
glog.Errorf("no RequestInfo found in context, handler chain must be wrong")
|
glog.Errorf("no RequestInfo found in context, handler chain must be wrong")
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
apiserverrequest "k8s.io/kubernetes/pkg/apiserver/request"
|
||||||
)
|
)
|
||||||
|
|
||||||
const globalTimeout = time.Minute
|
const globalTimeout = time.Minute
|
||||||
@ -46,7 +46,7 @@ func WithTimeoutForNonLongRunningRequests(handler http.Handler, requestContextMa
|
|||||||
return time.After(globalTimeout), ""
|
return time.After(globalTimeout), ""
|
||||||
}
|
}
|
||||||
|
|
||||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
requestInfo, ok := apiserverrequest.RequestInfoFrom(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return time.After(globalTimeout), ""
|
return time.After(globalTimeout), ""
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,13 @@ go_library(
|
|||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/apiserver:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/api/errors:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers/responsewriters:go_default_library",
|
||||||
"//pkg/runtime:go_default_library",
|
"//pkg/runtime:go_default_library",
|
||||||
|
"//pkg/runtime/schema:go_default_library",
|
||||||
"//vendor:github.com/emicklei/go-restful",
|
"//vendor:github.com/emicklei/go-restful",
|
||||||
|
"//vendor:github.com/golang/glog",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,12 +17,19 @@ limitations under the License.
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
rt "runtime"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apiserver"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIContainer is a restful container which in addition support registering
|
// APIContainer is a restful container which in addition support registering
|
||||||
@ -48,9 +55,42 @@ func NewAPIContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *APICon
|
|||||||
}
|
}
|
||||||
c.Container.ServeMux = mux
|
c.Container.ServeMux = mux
|
||||||
c.Container.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
|
c.Container.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
|
||||||
|
c.Container.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
|
||||||
apiserver.InstallRecoverHandler(s, c.Container)
|
logStackOnRecover(s, panicReason, httpWriter)
|
||||||
apiserver.InstallServiceErrorHandler(s, c.Container)
|
})
|
||||||
|
c.Container.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
||||||
|
serviceErrorHandler(s, serviceErr, request, response)
|
||||||
|
})
|
||||||
|
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Unify with RecoverPanics?
|
||||||
|
func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
|
||||||
|
for i := 2; ; i++ {
|
||||||
|
_, file, line, ok := rt.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
|
||||||
|
}
|
||||||
|
glog.Errorln(buffer.String())
|
||||||
|
|
||||||
|
headers := http.Header{}
|
||||||
|
if ct := w.Header().Get("Content-Type"); len(ct) > 0 {
|
||||||
|
headers.Set("Accept", ct)
|
||||||
|
}
|
||||||
|
responsewriters.ErrorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", api.Resource(""), "", "", 0, false), s, schema.GroupVersion{}, w, &http.Request{Header: headers})
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, resp *restful.Response) {
|
||||||
|
responsewriters.ErrorNegotiated(
|
||||||
|
apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", serviceErr.Message, 0, false),
|
||||||
|
s,
|
||||||
|
schema.GroupVersion{},
|
||||||
|
resp,
|
||||||
|
request.Request,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -22,7 +22,7 @@ go_library(
|
|||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/apis/meta/v1:go_default_library",
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
"//pkg/apiserver:go_default_library",
|
"//pkg/apiserver/handlers/responsewriters:go_default_library",
|
||||||
"//pkg/apiserver/metrics:go_default_library",
|
"//pkg/apiserver/metrics:go_default_library",
|
||||||
"//pkg/genericapiserver/mux:go_default_library",
|
"//pkg/genericapiserver/mux:go_default_library",
|
||||||
"//pkg/genericapiserver/openapi:go_default_library",
|
"//pkg/genericapiserver/openapi:go_default_library",
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
"k8s.io/kubernetes/pkg/apiserver"
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,6 +45,6 @@ func (i Index) Install(c *mux.APIContainer) {
|
|||||||
// Extract the paths handled using mux handler.
|
// Extract the paths handled using mux handler.
|
||||||
handledPaths = append(handledPaths, c.NonSwaggerRoutes.HandledPaths()...)
|
handledPaths = append(handledPaths, c.NonSwaggerRoutes.HandledPaths()...)
|
||||||
sort.Strings(handledPaths)
|
sort.Strings(handledPaths)
|
||||||
apiserver.WriteRawJSON(status, metav1.RootPaths{Paths: handledPaths}, w)
|
responsewriters.WriteRawJSON(status, metav1.RootPaths{Paths: handledPaths}, w)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apiserver"
|
"k8s.io/kubernetes/pkg/apiserver/handlers/responsewriters"
|
||||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||||
"k8s.io/kubernetes/pkg/version"
|
"k8s.io/kubernetes/pkg/version"
|
||||||
)
|
)
|
||||||
@ -54,5 +54,5 @@ func (v Version) Install(c *mux.APIContainer) {
|
|||||||
|
|
||||||
// handleVersion writes the server's version information.
|
// handleVersion writes the server's version information.
|
||||||
func (v Version) handleVersion(req *restful.Request, resp *restful.Response) {
|
func (v Version) handleVersion(req *restful.Request, resp *restful.Response) {
|
||||||
apiserver.WriteRawJSON(http.StatusOK, *v.Version, resp.ResponseWriter)
|
responsewriters.WriteRawJSON(http.StatusOK, *v.Version, resp.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
1
pkg/master/thirdparty/BUILD
vendored
1
pkg/master/thirdparty/BUILD
vendored
@ -19,6 +19,7 @@ go_library(
|
|||||||
"//pkg/apis/extensions:go_default_library",
|
"//pkg/apis/extensions:go_default_library",
|
||||||
"//pkg/apis/meta/v1:go_default_library",
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
"//pkg/apiserver:go_default_library",
|
"//pkg/apiserver:go_default_library",
|
||||||
|
"//pkg/apiserver/handlers:go_default_library",
|
||||||
"//pkg/genericapiserver:go_default_library",
|
"//pkg/genericapiserver:go_default_library",
|
||||||
"//pkg/registry/extensions/rest:go_default_library",
|
"//pkg/registry/extensions/rest:go_default_library",
|
||||||
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
|
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
|
||||||
|
3
pkg/master/thirdparty/thirdparty.go
vendored
3
pkg/master/thirdparty/thirdparty.go
vendored
@ -30,6 +30,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
"k8s.io/kubernetes/pkg/apiserver"
|
"k8s.io/kubernetes/pkg/apiserver"
|
||||||
|
apiserverhandlers "k8s.io/kubernetes/pkg/apiserver/handlers"
|
||||||
"k8s.io/kubernetes/pkg/genericapiserver"
|
"k8s.io/kubernetes/pkg/genericapiserver"
|
||||||
extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest"
|
extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest"
|
||||||
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
|
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
|
||||||
@ -51,7 +52,7 @@ func (d dynamicLister) ListAPIResources() []metav1.APIResource {
|
|||||||
return d.m.getExistingThirdPartyResources(d.path)
|
return d.m.getExistingThirdPartyResources(d.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ apiserver.APIResourceLister = &dynamicLister{}
|
var _ apiserverhandlers.APIResourceLister = &dynamicLister{}
|
||||||
|
|
||||||
type ThirdPartyResourceServer struct {
|
type ThirdPartyResourceServer struct {
|
||||||
genericAPIServer *genericapiserver.GenericAPIServer
|
genericAPIServer *genericapiserver.GenericAPIServer
|
||||||
|
Loading…
Reference in New Issue
Block a user