From 5ba06cf2bcecad892d449e6afbd09511da45b3af Mon Sep 17 00:00:00 2001 From: mbohlool Date: Sat, 8 Oct 2016 02:36:37 -0700 Subject: [PATCH] Make Kubernetes OpenAPI operation IDs unique --- cmd/kube-apiserver/app/server.go | 8 +- .../cmd/federation-apiserver/app/server.go | 6 +- hack/.linted_packages | 1 + pkg/apiserver/openapi/openapi.go | 81 +++++++++++++++++++ pkg/genericapiserver/config.go | 37 ++++----- pkg/genericapiserver/genericapiserver.go | 37 ++++----- pkg/genericapiserver/openapi/common/common.go | 26 +++++- pkg/genericapiserver/openapi/openapi.go | 80 +++++++----------- pkg/genericapiserver/openapi/openapi_test.go | 77 +++++++++--------- pkg/master/master_test.go | 15 ++-- pkg/util/trie.go | 21 +++-- test/integration/framework/master_utils.go | 8 +- 12 files changed, 244 insertions(+), 153 deletions(-) create mode 100644 pkg/apiserver/openapi/openapi.go diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index fe15e97fe23..1ab557476d5 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -42,13 +42,14 @@ import ( "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver/authenticator" + "k8s.io/kubernetes/pkg/apiserver/openapi" authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union" "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller/informers" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" - "k8s.io/kubernetes/pkg/generated/openapi" + generatedopenapi "k8s.io/kubernetes/pkg/generated/openapi" "k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/genericapiserver/authorizer" genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options" @@ -334,8 +335,9 @@ func Run(s *options.APIServer) error { genericConfig.ProxyDialer = proxyDialerFn genericConfig.ProxyTLSClientConfig = proxyTLSClientConfig genericConfig.Serializer = api.Codecs - genericConfig.OpenAPIInfo.Title = "Kubernetes" - genericConfig.OpenAPIDefinitions = openapi.OpenAPIDefinitions + genericConfig.OpenAPIConfig.Info.Title = "Kubernetes" + genericConfig.OpenAPIConfig.Definitions = generatedopenapi.OpenAPIDefinitions + genericConfig.OpenAPIConfig.GetOperationID = openapi.GetOperationID genericConfig.EnableOpenAPISupport = true config := &master.Config{ diff --git a/federation/cmd/federation-apiserver/app/server.go b/federation/cmd/federation-apiserver/app/server.go index 034555b95d7..d5924854194 100644 --- a/federation/cmd/federation-apiserver/app/server.go +++ b/federation/cmd/federation-apiserver/app/server.go @@ -34,6 +34,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apiserver/authenticator" + apiserveropenapi "k8s.io/kubernetes/pkg/apiserver/openapi" authorizerunion "k8s.io/kubernetes/pkg/auth/authorizer/union" "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/controller/informers" @@ -221,7 +222,10 @@ func Run(s *options.ServerRunOptions) error { genericConfig.APIResourceConfigSource = storageFactory.APIResourceConfigSource genericConfig.MasterServiceNamespace = s.MasterServiceNamespace genericConfig.Serializer = api.Codecs - genericConfig.OpenAPIDefinitions = openapi.OpenAPIDefinitions + genericConfig.OpenAPIConfig.Definitions = openapi.OpenAPIDefinitions + // Reusing api-server's GetOperationID function. if federation and api-server spec diverge and + // this method does not provide good operation IDs for federation, we should create federation's own GetOperationID. + genericConfig.OpenAPIConfig.GetOperationID = apiserveropenapi.GetOperationID genericConfig.EnableOpenAPISupport = true // TODO: Move this to generic api server (Need to move the command line flag). diff --git a/hack/.linted_packages b/hack/.linted_packages index 19d5c94df25..2644dfef4f1 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -76,6 +76,7 @@ pkg/apis/rbac/install pkg/apis/storage/install pkg/apis/storage/validation pkg/apiserver/audit +pkg/apiserver/openapi pkg/auth/authenticator pkg/auth/authorizer/union pkg/client/metrics diff --git a/pkg/apiserver/openapi/openapi.go b/pkg/apiserver/openapi/openapi.go new file mode 100644 index 00000000000..7a261c5174d --- /dev/null +++ b/pkg/apiserver/openapi/openapi.go @@ -0,0 +1,81 @@ +/* +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 openapi + +import ( + "bytes" + "fmt" + "strings" + "unicode" + + "github.com/emicklei/go-restful" + + "k8s.io/kubernetes/pkg/util" +) + +var verbs = util.CreateTrie([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"}) + +// ToValidOperationID makes an string a valid op ID (e.g. removing punctuations and whitespaces and make it camel case) +func ToValidOperationID(s string, capitalizeFirstLetter bool) string { + var buffer bytes.Buffer + capitalize := capitalizeFirstLetter + for i, r := range s { + if unicode.IsLetter(r) || r == '_' || (i != 0 && unicode.IsDigit(r)) { + if capitalize { + buffer.WriteRune(unicode.ToUpper(r)) + capitalize = false + } else { + buffer.WriteRune(r) + } + } else { + capitalize = true + } + } + return buffer.String() +} + +// GetOperationID returns a customize operation ID for kubernetes API server's OpenAPI spec to prevent duplicate IDs. +func GetOperationID(servePath string, r *restful.Route) (string, error) { + op := r.Operation + path := r.Path + // TODO: This is hacky, figure out where this name conflict is created and fix it at the root. + if strings.HasPrefix(path, "/apis/extensions/v1beta1/namespaces/{namespace}/") && strings.HasSuffix(op, "ScaleScale") { + op = op[:len(op)-10] + strings.Title(strings.Split(path[48:], "/")[0]) + "Scale" + } + switch servePath { + case "/swagger.json": + prefix, exists := verbs.GetPrefix(op) + if !exists { + return op, fmt.Errorf("operation names should start with a verb. Cannot determine operation verb from %v", op) + } + op = op[len(prefix):] + parts := strings.Split(strings.Trim(path, "/"), "/") + // Assume /api is /apis/core, remove this when we actually server /api/... on /apis/core/... + if len(parts) >= 1 && parts[0] == "api" { + parts = append([]string{"apis", "core"}, parts[1:]...) + } + if len(parts) >= 2 && parts[0] == "apis" { + prefix = prefix + ToValidOperationID(strings.TrimSuffix(parts[1], ".k8s.io"), prefix != "") + if len(parts) > 2 { + prefix = prefix + ToValidOperationID(parts[2], prefix != "") + } + } + return prefix + ToValidOperationID(op, prefix != ""), nil + default: + return op, nil + } +} diff --git a/pkg/genericapiserver/config.go b/pkg/genericapiserver/config.go index 8d7492370ca..5e651d0ed92 100644 --- a/pkg/genericapiserver/config.go +++ b/pkg/genericapiserver/config.go @@ -156,15 +156,8 @@ type Config struct { // EnableOpenAPISupport enables OpenAPI support. Allow downstream customers to disable OpenAPI spec. EnableOpenAPISupport bool - // OpenAPIInfo will be directly available as Info section of Open API spec. - OpenAPIInfo spec.Info - - // OpenAPIDefaultResponse will be used if an web service operation does not have any responses listed. - OpenAPIDefaultResponse spec.Response - - // OpenAPIDefinitions is a map of type to OpenAPI spec for all types used in this API server. Failure to provide - // this map or any of the models used by the server APIs will result in spec generation failure. - OpenAPIDefinitions *common.OpenAPIDefinitions + // OpenAPIConfig will be used in generating OpenAPI spec. + OpenAPIConfig *common.Config // MaxRequestsInFlight is the maximum number of parallel non-long-running requests. Every further // request has to wait. @@ -253,13 +246,19 @@ func NewConfig(options *options.ServerRunOptions) *Config { ReadWritePort: options.SecurePort, ServiceClusterIPRange: &options.ServiceClusterIPRange, ServiceNodePortRange: options.ServiceNodePortRange, - OpenAPIDefaultResponse: spec.Response{ - ResponseProps: spec.ResponseProps{ - Description: "Default Response."}}, - OpenAPIInfo: spec.Info{ - InfoProps: spec.InfoProps{ - Title: "Generic API Server", - Version: "unversioned", + OpenAPIConfig: &common.Config{ + ProtocolList: []string{"https"}, + IgnorePrefixes: []string{"/swaggerapi"}, + Info: &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Generic API Server", + Version: "unversioned", + }, + }, + DefaultResponse: &spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: "Default Response.", + }, }, }, MaxRequestsInFlight: options.MaxRequestsInFlight, @@ -386,10 +385,8 @@ func (c completedConfig) New() (*GenericAPIServer, error) { KubernetesServiceNodePort: c.KubernetesServiceNodePort, apiGroupsForDiscovery: map[string]unversioned.APIGroup{}, - enableOpenAPISupport: c.EnableOpenAPISupport, - openAPIInfo: c.OpenAPIInfo, - openAPIDefaultResponse: c.OpenAPIDefaultResponse, - openAPIDefinitions: c.OpenAPIDefinitions, + enableOpenAPISupport: c.EnableOpenAPISupport, + openAPIConfig: c.OpenAPIConfig, } s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer) diff --git a/pkg/genericapiserver/genericapiserver.go b/pkg/genericapiserver/genericapiserver.go index d53266dd8f4..5401903e0de 100644 --- a/pkg/genericapiserver/genericapiserver.go +++ b/pkg/genericapiserver/genericapiserver.go @@ -31,9 +31,9 @@ import ( systemd "github.com/coreos/go-systemd/daemon" "github.com/emicklei/go-restful" "github.com/emicklei/go-restful/swagger" - "github.com/go-openapi/spec" "github.com/golang/glog" + "github.com/go-openapi/spec" "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/rest" @@ -147,6 +147,11 @@ type GenericAPIServer struct { apiGroupsForDiscoveryLock sync.RWMutex apiGroupsForDiscovery map[string]unversioned.APIGroup + // See Config.$name for documentation of these flags + + enableOpenAPISupport bool + openAPIConfig *common.Config + // PostStartHooks are each called after the server has started listening, in a separate go func for each // with no guaranteee of ordering between them. The map key is a name used for error reporting. // It may kill the process with a panic if it wishes to by returning an error @@ -156,7 +161,6 @@ type GenericAPIServer struct { // See Config.$name for documentation of these flags: - enableOpenAPISupport bool openAPIInfo spec.Info openAPIDefaultResponse spec.Response openAPIDefinitions *common.OpenAPIDefinitions @@ -462,33 +466,20 @@ func (s *GenericAPIServer) InstallOpenAPI() { // Install one spec per web service, an ideal client will have a ClientSet containing one client // per each of these specs. for _, w := range s.HandlerContainer.RegisteredWebServices() { - if w.RootPath() == "/swaggerapi" { + if strings.HasPrefix(w.RootPath(), "/swaggerapi") { continue } - info := s.openAPIInfo - info.Title = info.Title + " " + w.RootPath() - err := openapi.RegisterOpenAPIService(&openapi.Config{ - OpenAPIServePath: w.RootPath() + "/swagger.json", - WebServices: []*restful.WebService{w}, - ProtocolList: []string{"https"}, - IgnorePrefixes: []string{"/swaggerapi"}, - Info: &info, - DefaultResponse: &s.openAPIDefaultResponse, - OpenAPIDefinitions: s.openAPIDefinitions, - }, s.HandlerContainer.Container) + config := *s.openAPIConfig + config.Info = new(spec.Info) + *config.Info = *s.openAPIConfig.Info + config.Info.Title = config.Info.Title + " " + w.RootPath() + err := openapi.RegisterOpenAPIService(w.RootPath()+"/swagger.json", []*restful.WebService{w}, &config, s.HandlerContainer.Container) if err != nil { glog.Fatalf("Failed to register open api spec for %v: %v", w.RootPath(), err) } } - err := openapi.RegisterOpenAPIService(&openapi.Config{ - OpenAPIServePath: "/swagger.json", - WebServices: s.HandlerContainer.RegisteredWebServices(), - ProtocolList: []string{"https"}, - IgnorePrefixes: []string{"/swaggerapi"}, - Info: &s.openAPIInfo, - DefaultResponse: &s.openAPIDefaultResponse, - OpenAPIDefinitions: s.openAPIDefinitions, - }, s.HandlerContainer.Container) + err := openapi.RegisterOpenAPIService("/swagger.json", s.HandlerContainer.RegisteredWebServices(), s.openAPIConfig, s.HandlerContainer.Container) + if err != nil { glog.Fatalf("Failed to register open api spec for root: %v", err) } diff --git a/pkg/genericapiserver/openapi/common/common.go b/pkg/genericapiserver/openapi/common/common.go index 48c4f9c0191..3bcc744dfad 100644 --- a/pkg/genericapiserver/openapi/common/common.go +++ b/pkg/genericapiserver/openapi/common/common.go @@ -16,7 +16,10 @@ limitations under the License. package common -import "github.com/go-openapi/spec" +import ( + "github.com/emicklei/go-restful" + "github.com/go-openapi/spec" +) // OpenAPIDefinition describes single type. Normally these definitions are auto-generated using gen-openapi. type OpenAPIDefinition struct { @@ -35,6 +38,27 @@ type OpenAPIDefinitionGetter interface { OpenAPIDefinition() *OpenAPIDefinition } +// Config is set of configuration for openAPI spec generation. +type Config struct { + // List of supported protocols such as https, http, etc. + ProtocolList []string + + // Info is general information about the API. + Info *spec.Info + // DefaultResponse will be used if an operation does not have any responses listed. It + // will show up as ... "responses" : {"default" : $DefaultResponse} in the spec. + DefaultResponse *spec.Response + // List of webservice's path prefixes to ignore + IgnorePrefixes []string + + // OpenAPIDefinitions should provide definition for all models used by routes. Failure to provide this map + // or any of the models will result in spec generation failure. + Definitions *OpenAPIDefinitions + + // GetOperationID returns operation id for a restful route. It is an optional function to customize operation IDs. + GetOperationID func(servePath string, r *restful.Route) (string, error) +} + // This function is a reference for converting go (or any custom type) to a simple open API type,format pair. There are // two ways to customize spec for a type. If you add it here, a type will be converted to a simple type and the type // comment (the comment that is added before type definition) will be lost. The spec will still have the property diff --git a/pkg/genericapiserver/openapi/openapi.go b/pkg/genericapiserver/openapi/openapi.go index 88adb767a3a..b251272ebb9 100644 --- a/pkg/genericapiserver/openapi/openapi.go +++ b/pkg/genericapiserver/openapi/openapi.go @@ -26,47 +26,26 @@ import ( "github.com/go-openapi/spec" "k8s.io/kubernetes/pkg/genericapiserver/openapi/common" - "k8s.io/kubernetes/pkg/util/json" "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/json" ) const ( OpenAPIVersion = "2.0" ) -// Config is set of configuration for openAPI spec generation. -type Config struct { - // Path to the spec file. by convention, it should name [.*/]*/swagger.json - OpenAPIServePath string - // List of web services for this API spec - WebServices []*restful.WebService - - // List of supported protocols such as https, http, etc. - ProtocolList []string - - // Info is general information about the API. - Info *spec.Info - // DefaultResponse will be used if an operation does not have any responses listed. It - // will show up as ... "responses" : {"default" : $DefaultResponse} in the spec. - DefaultResponse *spec.Response - // List of webservice's path prefixes to ignore - IgnorePrefixes []string - - // OpenAPIDefinitions should provide definition for all models used by routes. Failure to provide this map - // or any of the models will result in spec generation failure. - OpenAPIDefinitions *common.OpenAPIDefinitions -} - type openAPI struct { - config *Config + config *common.Config swagger *spec.Swagger protocolList []string + servePath string } // RegisterOpenAPIService registers a handler to provides standard OpenAPI specification. -func RegisterOpenAPIService(config *Config, containers *restful.Container) (err error) { +func RegisterOpenAPIService(servePath string, webServices []*restful.WebService, config *common.Config, containers *restful.Container) (err error) { o := openAPI{ - config: config, + config: config, + servePath: servePath, swagger: &spec.Swagger{ SwaggerProps: spec.SwaggerProps{ Swagger: OpenAPIVersion, @@ -77,14 +56,14 @@ func RegisterOpenAPIService(config *Config, containers *restful.Container) (err }, } - err = o.init() + err = o.init(webServices) if err != nil { return err } - containers.ServeMux.HandleFunc(config.OpenAPIServePath, func(w http.ResponseWriter, r *http.Request) { + containers.ServeMux.HandleFunc(servePath, func(w http.ResponseWriter, r *http.Request) { resp := restful.NewResponse(w) - if r.URL.Path != config.OpenAPIServePath { + if r.URL.Path != servePath { resp.WriteErrorString(http.StatusNotFound, "Path not found!") } // TODO: we can cache json string and return it here. @@ -93,8 +72,13 @@ func RegisterOpenAPIService(config *Config, containers *restful.Container) (err return nil } -func (o *openAPI) init() error { - err := o.buildPaths() +func (o *openAPI) init(webServices []*restful.WebService) error { + if o.config.GetOperationID == nil { + o.config.GetOperationID = func(_ string, r *restful.Route) (string, error) { + return r.Operation, nil + } + } + err := o.buildPaths(webServices) if err != nil { return err } @@ -105,7 +89,7 @@ func (o *openAPI) buildDefinitionRecursively(name string) error { if _, ok := o.swagger.Definitions[name]; ok { return nil } - if item, ok := (*o.config.OpenAPIDefinitions)[name]; ok { + if item, ok := (*o.config.Definitions)[name]; ok { o.swagger.Definitions[name] = item.Schema for _, v := range item.Dependencies { if err := o.buildDefinitionRecursively(v); err != nil { @@ -134,20 +118,10 @@ func (o *openAPI) buildDefinitionForType(sample interface{}) (string, error) { } // buildPaths builds OpenAPI paths using go-restful's web services. -func (o *openAPI) buildPaths() error { +func (o *openAPI) buildPaths(webServices []*restful.WebService) error { pathsToIgnore := util.CreateTrie(o.config.IgnorePrefixes) - duplicateOpId := make(map[string]bool) - // Find duplicate operation IDs. - for _, service := range o.config.WebServices { - if pathsToIgnore.HasPrefix(service.RootPath()) { - continue - } - for _, route := range service.Routes() { - _, exists := duplicateOpId[route.Operation] - duplicateOpId[route.Operation] = exists - } - } - for _, w := range o.config.WebServices { + duplicateOpId := make(map[string]string) + for _, w := range webServices { rootPath := w.RootPath() if pathsToIgnore.HasPrefix(rootPath) { continue @@ -191,11 +165,11 @@ func (o *openAPI) buildPaths() error { if err != nil { return err } - if duplicateOpId[op.ID] { - // Repeated Operation IDs are not allowed in OpenAPI spec but if - // an OperationID is empty, client generators will infer the ID - // from the path and method of operation. - op.ID = "" + dpath, exists := duplicateOpId[op.ID] + if exists { + return fmt.Errorf("Duplicate Operation ID %v for path %v and %v.", op.ID, dpath, path) + } else { + duplicateOpId[op.ID] = path } switch strings.ToUpper(route.Method) { case "GET": @@ -227,7 +201,6 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map Description: route.Doc, Consumes: route.Consumes, Produces: route.Produces, - ID: route.Operation, Schemes: o.config.ProtocolList, Responses: &spec.Responses{ ResponsesProps: spec.ResponsesProps{ @@ -236,6 +209,9 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map }, }, } + if ret.ID, err = o.config.GetOperationID(o.servePath, &route); err != nil { + return ret, err + } // Build responses for _, resp := range route.ResponseErrors { diff --git a/pkg/genericapiserver/openapi/openapi_test.go b/pkg/genericapiserver/openapi/openapi_test.go index 9816a916b4b..9975a8306a0 100644 --- a/pkg/genericapiserver/openapi/openapi_test.go +++ b/pkg/genericapiserver/openapi/openapi_test.go @@ -28,9 +28,9 @@ import ( ) // setUp is a convenience function for setting up for (most) tests. -func setUp(t *testing.T, fullMethods bool) (openAPI, *assert.Assertions) { +func setUp(t *testing.T, fullMethods bool) (openAPI, *restful.Container, *assert.Assertions) { assert := assert.New(t) - config := getConfig(fullMethods) + config, container := getConfig(fullMethods) return openAPI{ config: config, swagger: &spec.Swagger{ @@ -41,7 +41,7 @@ func setUp(t *testing.T, fullMethods bool) (openAPI, *assert.Assertions) { Info: config.Info, }, }, - }, assert + }, container, assert } func noOp(request *restful.Request, response *restful.Response) {} @@ -130,11 +130,11 @@ func (_ TestOutput) OpenAPIDefinition() *common.OpenAPIDefinition { var _ common.OpenAPIDefinitionGetter = TestInput{} var _ common.OpenAPIDefinitionGetter = TestOutput{} -func getTestRoute(ws *restful.WebService, method string, additionalParams bool) *restful.RouteBuilder { +func getTestRoute(ws *restful.WebService, method string, additionalParams bool, opPrefix string) *restful.RouteBuilder { ret := ws.Method(method). Path("/test/{path:*}"). Doc(fmt.Sprintf("%s test input", method)). - Operation(fmt.Sprintf("%sTestInput", method)). + Operation(fmt.Sprintf("%s%sTestInput", method, opPrefix)). Produces(restful.MIME_JSON). Consumes(restful.MIME_JSON). Param(ws.PathParameter("path", "path to the resource").DataType("string")). @@ -150,52 +150,50 @@ func getTestRoute(ws *restful.WebService, method string, additionalParams bool) return ret } -func getConfig(fullMethods bool) *Config { +func getConfig(fullMethods bool) (*common.Config, *restful.Container) { mux := http.NewServeMux() container := restful.NewContainer() container.ServeMux = mux ws := new(restful.WebService) ws.Path("/foo") - ws.Route(getTestRoute(ws, "get", true)) + ws.Route(getTestRoute(ws, "get", true, "foo")) if fullMethods { - ws.Route(getTestRoute(ws, "post", false)). - Route(getTestRoute(ws, "put", false)). - Route(getTestRoute(ws, "head", false)). - Route(getTestRoute(ws, "patch", false)). - Route(getTestRoute(ws, "options", false)). - Route(getTestRoute(ws, "delete", false)) + ws.Route(getTestRoute(ws, "post", false, "foo")). + Route(getTestRoute(ws, "put", false, "foo")). + Route(getTestRoute(ws, "head", false, "foo")). + Route(getTestRoute(ws, "patch", false, "foo")). + Route(getTestRoute(ws, "options", false, "foo")). + Route(getTestRoute(ws, "delete", false, "foo")) } ws.Path("/bar") - ws.Route(getTestRoute(ws, "get", true)) + ws.Route(getTestRoute(ws, "get", true, "bar")) if fullMethods { - ws.Route(getTestRoute(ws, "post", false)). - Route(getTestRoute(ws, "put", false)). - Route(getTestRoute(ws, "head", false)). - Route(getTestRoute(ws, "patch", false)). - Route(getTestRoute(ws, "options", false)). - Route(getTestRoute(ws, "delete", false)) + ws.Route(getTestRoute(ws, "post", false, "bar")). + Route(getTestRoute(ws, "put", false, "bar")). + Route(getTestRoute(ws, "head", false, "bar")). + Route(getTestRoute(ws, "patch", false, "bar")). + Route(getTestRoute(ws, "options", false, "bar")). + Route(getTestRoute(ws, "delete", false, "bar")) } container.Add(ws) - return &Config{ - WebServices: container.RegisteredWebServices(), - ProtocolList: []string{"https"}, - OpenAPIServePath: "/swagger.json", + return &common.Config{ + ProtocolList: []string{"https"}, Info: &spec.Info{ InfoProps: spec.InfoProps{ Title: "TestAPI", Description: "Test API", }, }, - OpenAPIDefinitions: &common.OpenAPIDefinitions{ + Definitions: &common.OpenAPIDefinitions{ "openapi.TestInput": *TestInput{}.OpenAPIDefinition(), "openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(), }, - } + }, container } -func getTestOperation(method string) *spec.Operation { +func getTestOperation(method string, opPrefix string) *spec.Operation { return &spec.Operation{ OperationProps: spec.OperationProps{ Description: fmt.Sprintf("%s test input", method), @@ -204,25 +202,26 @@ func getTestOperation(method string) *spec.Operation { Schemes: []string{"https"}, Parameters: []spec.Parameter{}, Responses: getTestResponses(), + ID: fmt.Sprintf("%s%sTestInput", method, opPrefix), }, } } -func getTestPathItem(allMethods bool) spec.PathItem { +func getTestPathItem(allMethods bool, opPrefix string) spec.PathItem { ret := spec.PathItem{ PathItemProps: spec.PathItemProps{ - Get: getTestOperation("get"), + Get: getTestOperation("get", opPrefix), Parameters: getTestCommonParameters(), }, } ret.Get.Parameters = getAdditionalTestParameters() if allMethods { - ret.PathItemProps.Put = getTestOperation("put") - ret.PathItemProps.Post = getTestOperation("post") - ret.PathItemProps.Head = getTestOperation("head") - ret.PathItemProps.Patch = getTestOperation("patch") - ret.PathItemProps.Delete = getTestOperation("delete") - ret.PathItemProps.Options = getTestOperation("options") + ret.PathItemProps.Put = getTestOperation("put", opPrefix) + ret.PathItemProps.Post = getTestOperation("post", opPrefix) + ret.PathItemProps.Head = getTestOperation("head", opPrefix) + ret.PathItemProps.Patch = getTestOperation("patch", opPrefix) + ret.PathItemProps.Delete = getTestOperation("delete", opPrefix) + ret.PathItemProps.Options = getTestOperation("options", opPrefix) } return ret } @@ -380,7 +379,7 @@ func getTestOutputDefinition() spec.Schema { } func TestBuildSwaggerSpec(t *testing.T) { - o, assert := setUp(t, true) + o, container, assert := setUp(t, true) expected := &spec.Swagger{ SwaggerProps: spec.SwaggerProps{ Info: &spec.Info{ @@ -392,8 +391,8 @@ func TestBuildSwaggerSpec(t *testing.T) { Swagger: "2.0", Paths: &spec.Paths{ Paths: map[string]spec.PathItem{ - "/foo/test/{path}": getTestPathItem(true), - "/bar/test/{path}": getTestPathItem(true), + "/foo/test/{path}": getTestPathItem(true, "foo"), + "/bar/test/{path}": getTestPathItem(true, "bar"), }, }, Definitions: spec.Definitions{ @@ -402,7 +401,7 @@ func TestBuildSwaggerSpec(t *testing.T) { }, }, } - err := o.init() + err := o.init(container.RegisteredWebServices()) if assert.NoError(err) { assert.Equal(expected, o.swagger) } diff --git a/pkg/master/master_test.go b/pkg/master/master_test.go index 655de7612e7..be622e07235 100644 --- a/pkg/master/master_test.go +++ b/pkg/master/master_test.go @@ -44,10 +44,12 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions" extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/apiserver/openapi" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/client/restclient" - "k8s.io/kubernetes/pkg/generated/openapi" + openapigen "k8s.io/kubernetes/pkg/generated/openapi" "k8s.io/kubernetes/pkg/genericapiserver" + "k8s.io/kubernetes/pkg/genericapiserver/openapi/common" "k8s.io/kubernetes/pkg/kubelet/client" ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" "k8s.io/kubernetes/pkg/registry/registrytest" @@ -69,7 +71,9 @@ func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert. server, storageConfig := etcdtesting.NewUnsecuredEtcd3TestClientServer(t) config := &Config{ - GenericConfig: &genericapiserver.Config{}, + GenericConfig: &genericapiserver.Config{ + OpenAPIConfig: &common.Config{}, + }, } resourceEncoding := genericapiserver.NewDefaultResourceEncodingConfig() @@ -499,15 +503,16 @@ func TestValidOpenAPISpec(t *testing.T) { _, etcdserver, config, assert := setUp(t) defer etcdserver.Terminate(t) - config.GenericConfig.OpenAPIDefinitions = openapi.OpenAPIDefinitions + config.GenericConfig.OpenAPIConfig.Definitions = openapigen.OpenAPIDefinitions config.GenericConfig.EnableOpenAPISupport = true config.GenericConfig.EnableIndex = true - config.GenericConfig.OpenAPIInfo = spec.Info{ + config.GenericConfig.OpenAPIConfig.Info = &spec.Info{ InfoProps: spec.InfoProps{ Title: "Kubernetes", Version: "unversioned", }, } + config.GenericConfig.OpenAPIConfig.GetOperationID = openapi.GetOperationID master, err := config.Complete().New() if err != nil { t.Fatalf("Error in bringing up the master: %v", err) @@ -592,7 +597,7 @@ func TestValidOpenAPISpec(t *testing.T) { t.Logf("Open API spec on %v has some warnings : %v", path, warns) } } else { - t.Logf("Validation is disabled because it is timing out on jenkins put passing locally.") + t.Logf("Validation is disabled because it is timing out on jenkins but passing locally.") } } diff --git a/pkg/util/trie.go b/pkg/util/trie.go index 07e0be75d7d..0eeac436dc9 100644 --- a/pkg/util/trie.go +++ b/pkg/util/trie.go @@ -20,8 +20,10 @@ package util type Trie struct { children map[byte]*Trie wordTail bool + word string } +// CreateTrie creates a Trie and add all strings in the provided list to it. func CreateTrie(list []string) Trie { ret := Trie{ children: make(map[byte]*Trie), @@ -33,6 +35,7 @@ func CreateTrie(list []string) Trie { return ret } +// Add adds a word to this trie func (t *Trie) Add(v string) { root := t for _, b := range []byte(v) { @@ -47,24 +50,30 @@ func (t *Trie) Add(v string) { root = child } root.wordTail = true + root.word = v } +// HasPrefix returns true of v has any of the prefixes stored in this trie. func (t *Trie) HasPrefix(v string) bool { + _, has := t.GetPrefix(v) + return has +} + +// GetPrefix is like HasPrefix but return the prefix in case of match or empty string otherwise. +func (t *Trie) GetPrefix(v string) (string, bool) { root := t if root.wordTail { - return true + return root.word, true } for _, b := range []byte(v) { child, exists := root.children[b] if !exists { - return false + return "", false } if child.wordTail { - return true + return child.word, true } root = child } - return false + return "", false } - - diff --git a/test/integration/framework/master_utils.go b/test/integration/framework/master_utils.go index 0ab1b10c715..706bcd5c8d8 100644 --- a/test/integration/framework/master_utils.go +++ b/test/integration/framework/master_utils.go @@ -68,6 +68,7 @@ import ( "github.com/go-openapi/spec" "github.com/pborman/uuid" + "k8s.io/kubernetes/pkg/genericapiserver/openapi/common" ) const ( @@ -184,17 +185,18 @@ func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Serv masterConfig.GenericConfig.EnableProfiling = true masterConfig.GenericConfig.EnableSwaggerSupport = true masterConfig.GenericConfig.EnableOpenAPISupport = true - masterConfig.GenericConfig.OpenAPIInfo = spec.Info{ + masterConfig.GenericConfig.OpenAPIConfig.Info = &spec.Info{ InfoProps: spec.InfoProps{ Title: "Kubernetes", Version: "unversioned", }, } - masterConfig.GenericConfig.OpenAPIDefaultResponse = spec.Response{ + masterConfig.GenericConfig.OpenAPIConfig.DefaultResponse = &spec.Response{ ResponseProps: spec.ResponseProps{ Description: "Default Response.", }, } + masterConfig.GenericConfig.OpenAPIConfig.Definitions = openapi.OpenAPIDefinitions } // set the loopback client config @@ -355,8 +357,8 @@ func NewMasterConfig() *master.Config { ServiceClusterIPRange: parseCIDROrDie("10.0.0.0/24"), ServiceNodePortRange: utilnet.PortRange{Base: 30000, Size: 2768}, EnableVersion: true, - OpenAPIDefinitions: openapi.OpenAPIDefinitions, EnableOpenAPISupport: true, + OpenAPIConfig: &common.Config{}, }, StorageFactory: storageFactory, EnableCoreControllers: true,