Merge pull request #33629 from mbohlool/o2

Automatic merge from submit-queue

Generate unique Operation IDs for root OpenAPI spec

This PR adds a customization method GetOperationID to OpenAPI spec generation and then use it to make sure root spec has unique operation IDs by mostly adding GroupVersion to the start of operation ID.
This commit is contained in:
Kubernetes Submit Queue 2016-10-12 21:06:24 -07:00 committed by GitHub
commit adfbe8d952
16 changed files with 853 additions and 634 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1245,6 +1245,7 @@
"schemes": [
"https"
],
"operationId": "readNamespacedDeploymentsScale",
"responses": {
"200": {
"description": "OK",
@ -1267,6 +1268,7 @@
"schemes": [
"https"
],
"operationId": "replaceNamespacedDeploymentsScale",
"parameters": [
{
"name": "body",
@ -1301,6 +1303,7 @@
"schemes": [
"https"
],
"operationId": "patchNamespacedDeploymentsScale",
"parameters": [
{
"name": "body",
@ -3527,6 +3530,7 @@
"schemes": [
"https"
],
"operationId": "readNamespacedReplicasetsScale",
"responses": {
"200": {
"description": "OK",
@ -3549,6 +3553,7 @@
"schemes": [
"https"
],
"operationId": "replaceNamespacedReplicasetsScale",
"parameters": [
{
"name": "body",
@ -3583,6 +3588,7 @@
"schemes": [
"https"
],
"operationId": "patchNamespacedReplicasetsScale",
"parameters": [
{
"name": "body",
@ -3760,6 +3766,7 @@
"schemes": [
"https"
],
"operationId": "readNamespacedReplicationcontrollersScale",
"responses": {
"200": {
"description": "OK",
@ -3782,6 +3789,7 @@
"schemes": [
"https"
],
"operationId": "replaceNamespacedReplicationcontrollersScale",
"parameters": [
{
"name": "body",
@ -3816,6 +3824,7 @@
"schemes": [
"https"
],
"operationId": "patchNamespacedReplicationcontrollersScale",
"parameters": [
{
"name": "body",

View File

@ -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{

View File

@ -21,6 +21,7 @@
"schemes": [
"https"
],
"operationId": "getCoreAPIVersions",
"responses": {
"200": {
"description": "OK",
@ -47,6 +48,7 @@
"schemes": [
"https"
],
"operationId": "getCoreV1APIResources",
"responses": {
"200": {
"description": "OK",
@ -2748,6 +2750,7 @@
"schemes": [
"https"
],
"operationId": "getAPIVersions",
"responses": {
"200": {
"description": "OK",
@ -2774,6 +2777,7 @@
"schemes": [
"https"
],
"operationId": "getExtensionsAPIGroup",
"responses": {
"200": {
"description": "OK",
@ -2800,6 +2804,7 @@
"schemes": [
"https"
],
"operationId": "getExtensionsV1beta1APIResources",
"responses": {
"200": {
"description": "OK",
@ -4464,6 +4469,7 @@
"schemes": [
"https"
],
"operationId": "getFederationAPIGroup",
"responses": {
"200": {
"description": "OK",
@ -4490,6 +4496,7 @@
"schemes": [
"https"
],
"operationId": "getFederationV1beta1APIResources",
"responses": {
"200": {
"description": "OK",

View File

@ -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).

View File

@ -77,6 +77,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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

@ -26,6 +26,7 @@ import (
"github.com/go-openapi/spec"
"k8s.io/kubernetes/pkg/genericapiserver/openapi/common"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/json"
)
@ -33,39 +34,18 @@ 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,
@ -76,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.
@ -92,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
}
@ -104,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 {
@ -133,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 {
pathsToIgnore := 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 {
func (o *openAPI) buildPaths(webServices []*restful.WebService) error {
pathsToIgnore := util.CreateTrie(o.config.IgnorePrefixes)
duplicateOpId := make(map[string]string)
for _, w := range webServices {
rootPath := w.RootPath()
if pathsToIgnore.HasPrefix(rootPath) {
continue
@ -190,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":
@ -226,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{
@ -235,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 {

View File

@ -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)
}

View File

@ -59,54 +59,3 @@ func mapKeyFromParam(param *restful.Parameter) interface{} {
Kind: param.Data().Kind,
}
}
// A simple trie implementation with Add an HasPrefix methods only.
type trie struct {
children map[byte]*trie
wordTail bool
}
func createTrie(list []string) trie {
ret := trie{
children: make(map[byte]*trie),
wordTail: false,
}
for _, v := range list {
ret.Add(v)
}
return ret
}
func (t *trie) Add(v string) {
root := t
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
child = &trie{
children: make(map[byte]*trie),
wordTail: false,
}
root.children[b] = child
}
root = child
}
root.wordTail = true
}
func (t *trie) HasPrefix(v string) bool {
root := t
if root.wordTail {
return true
}
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
return false
}
if child.wordTail {
return true
}
root = child
}
return false
}

View File

@ -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.")
}
}

79
pkg/util/trie.go Normal file
View File

@ -0,0 +1,79 @@
/*
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 util
// A simple trie implementation with Add an HasPrefix methods only.
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),
wordTail: false,
}
for _, v := range list {
ret.Add(v)
}
return ret
}
// Add adds a word to this trie
func (t *Trie) Add(v string) {
root := t
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
child = &Trie{
children: make(map[byte]*Trie),
wordTail: false,
}
root.children[b] = child
}
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 root.word, true
}
for _, b := range []byte(v) {
child, exists := root.children[b]
if !exists {
return "", false
}
if child.wordTail {
return child.word, true
}
root = child
}
return "", false
}

View File

@ -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,