diff --git a/cmd/kube-apiserver/app/aggregator.go b/cmd/kube-apiserver/app/aggregator.go index bf7530104da..2c15c6a200e 100644 --- a/cmd/kube-apiserver/app/aggregator.go +++ b/cmd/kube-apiserver/app/aggregator.go @@ -51,6 +51,7 @@ func createAggregatorConfig(kubeAPIServerConfig genericapiserver.Config, command // the aggregator doesn't wire these up. It just delegates them to the kubeapiserver genericConfig.EnableSwaggerUI = false + genericConfig.OpenAPIConfig = nil genericConfig.SwaggerConfig = nil // copy the etcd options so we don't mutate originals. diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index d88a2d8973b..c9df180590f 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -27,7 +27,6 @@ import ( "github.com/emicklei/go-restful-swagger12" "github.com/golang/glog" - "github.com/go-openapi/spec" "k8s.io/apimachinery/pkg/apimachinery" "k8s.io/apimachinery/pkg/apimachinery/registered" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -43,7 +42,6 @@ import ( apirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/server/healthz" - "k8s.io/apiserver/pkg/server/openapi" "k8s.io/apiserver/pkg/server/routes" restclient "k8s.io/client-go/rest" ) @@ -131,9 +129,6 @@ type GenericAPIServer struct { swaggerConfig *swagger.Config openAPIConfig *openapicommon.Config - // Enables updating OpenAPI spec using update method. - OpenAPIService *openapi.OpenAPIService - // PostStartHooks are each called after the server has started listening, in a separate go func for each // with no guarantee 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. @@ -173,9 +168,6 @@ type DelegationTarget interface { // ListedPaths returns the paths for supporting an index ListedPaths() []string - - // OpenAPISpec returns the OpenAPI spec of the delegation target if exists, nil otherwise. - OpenAPISpec() *spec.Swagger } func (s *GenericAPIServer) UnprotectedHandler() http.Handler { @@ -191,9 +183,6 @@ func (s *GenericAPIServer) HealthzChecks() []healthz.HealthzChecker { func (s *GenericAPIServer) ListedPaths() []string { return s.listedPathProvider.ListedPaths() } -func (s *GenericAPIServer) OpenAPISpec() *spec.Swagger { - return s.OpenAPIService.GetSpec() -} var EmptyDelegate = emptyDelegate{ requestContextMapper: apirequest.NewRequestContextMapper(), @@ -218,9 +207,6 @@ func (s emptyDelegate) ListedPaths() []string { func (s emptyDelegate) RequestContextMapper() apirequest.RequestContextMapper { return s.requestContextMapper } -func (s emptyDelegate) OpenAPISpec() *spec.Swagger { - return nil -} // RequestContextMapper is exposed so that third party resource storage can be build in a different location. // TODO refactor third party resource storage @@ -243,22 +229,17 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer { if s.swaggerConfig != nil { routes.Swagger{Config: s.swaggerConfig}.Install(s.Handler.GoRestfulContainer) } - s.PrepareOpenAPIService() + if s.openAPIConfig != nil { + routes.OpenAPI{ + Config: s.openAPIConfig, + }.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux) + } s.installHealthz() return preparedGenericAPIServer{s} } -// PrepareOpenAPIService installs OpenAPI handler if it does not exists. -func (s *GenericAPIServer) PrepareOpenAPIService() { - if s.openAPIConfig != nil && s.OpenAPIService == nil { - s.OpenAPIService = routes.OpenAPI{ - Config: s.openAPIConfig, - }.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux) - } -} - // Run spawns the secure http server. It only returns if stopCh is closed // or the secure port cannot be listened on initially. func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error { diff --git a/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_aggregator.go b/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_aggregator.go deleted file mode 100644 index c1f0755a423..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_aggregator.go +++ /dev/null @@ -1,482 +0,0 @@ -/* -Copyright 2017 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 ( - "fmt" - "strings" - - "github.com/go-openapi/spec" - - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/apiserver/pkg/util/trie" -) - -const ( - DEFINITION_PREFIX = "#/definitions/" -) - -var cloner = conversion.NewCloner() - -// Run a walkRefCallback method on all references of an OpenAPI spec -type walkAllRefs struct { - // walkRefCallback will be called on each reference and the return value - // will replace that reference. This will allow the callers to change - // all/some references of an spec (e.g. useful in renaming definitions). - walkRefCallback func(ref spec.Ref) spec.Ref - - // The spec to walk through. - root *spec.Swagger -} - -func newWalkAllRefs(walkRef func(ref spec.Ref) spec.Ref, sp *spec.Swagger) *walkAllRefs { - return &walkAllRefs{ - walkRefCallback: walkRef, - root: sp, - } -} - -func (s *walkAllRefs) walkRef(ref spec.Ref) spec.Ref { - if ref.String() != "" { - refStr := ref.String() - // References that start with #/definitions/ has a definition - // inside the same spec file. If that is the case, walk through - // those definitions too. - // We do not support external references yet. - if strings.HasPrefix(refStr, DEFINITION_PREFIX) { - def := s.root.Definitions[refStr[len(DEFINITION_PREFIX):]] - s.walkSchema(&def) - } - } - return s.walkRefCallback(ref) -} - -func (s *walkAllRefs) walkSchema(schema *spec.Schema) { - if schema == nil { - return - } - schema.Ref = s.walkRef(schema.Ref) - for _, v := range schema.Definitions { - s.walkSchema(&v) - } - for _, v := range schema.Properties { - s.walkSchema(&v) - } - for _, v := range schema.PatternProperties { - s.walkSchema(&v) - } - for _, v := range schema.AllOf { - s.walkSchema(&v) - } - for _, v := range schema.AnyOf { - s.walkSchema(&v) - } - for _, v := range schema.OneOf { - s.walkSchema(&v) - } - if schema.Not != nil { - s.walkSchema(schema.Not) - } - if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { - s.walkSchema(schema.AdditionalProperties.Schema) - } - if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil { - s.walkSchema(schema.AdditionalItems.Schema) - } - if schema.Items != nil { - if schema.Items.Schema != nil { - s.walkSchema(schema.Items.Schema) - } - for _, v := range schema.Items.Schemas { - s.walkSchema(&v) - } - } -} - -func (s *walkAllRefs) walkParams(params []spec.Parameter) { - if params == nil { - return - } - for _, param := range params { - param.Ref = s.walkRef(param.Ref) - s.walkSchema(param.Schema) - if param.Items != nil { - param.Items.Ref = s.walkRef(param.Items.Ref) - } - } -} - -func (s *walkAllRefs) walkResponse(resp *spec.Response) { - if resp == nil { - return - } - resp.Ref = s.walkRef(resp.Ref) - s.walkSchema(resp.Schema) -} - -func (s *walkAllRefs) walkOperation(op *spec.Operation) { - if op == nil { - return - } - s.walkParams(op.Parameters) - if op.Responses == nil { - return - } - s.walkResponse(op.Responses.Default) - for _, r := range op.Responses.StatusCodeResponses { - s.walkResponse(&r) - } -} - -func (s *walkAllRefs) Start() { - for _, pathItem := range s.root.Paths.Paths { - s.walkParams(pathItem.Parameters) - s.walkOperation(pathItem.Delete) - s.walkOperation(pathItem.Get) - s.walkOperation(pathItem.Head) - s.walkOperation(pathItem.Options) - s.walkOperation(pathItem.Patch) - s.walkOperation(pathItem.Post) - s.walkOperation(pathItem.Put) - } -} - -// FilterSpecByPaths remove unnecessary paths and unused definitions. -func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) { - // First remove unwanted paths - prefixes := trie.New(keepPathPrefixes) - orgPaths := sp.Paths - if orgPaths == nil { - return - } - sp.Paths = &spec.Paths{ - VendorExtensible: orgPaths.VendorExtensible, - Paths: map[string]spec.PathItem{}, - } - for path, pathItem := range orgPaths.Paths { - if !prefixes.HasPrefix(path) { - continue - } - sp.Paths.Paths[path] = pathItem - } - - // Walk all references to find all definition references. - usedDefinitions := map[string]bool{} - - newWalkAllRefs(func(ref spec.Ref) spec.Ref { - if ref.String() != "" { - refStr := ref.String() - if strings.HasPrefix(refStr, DEFINITION_PREFIX) { - usedDefinitions[refStr[len(DEFINITION_PREFIX):]] = true - } - } - return ref - }, sp).Start() - - // Remove unused definitions - orgDefinitions := sp.Definitions - sp.Definitions = spec.Definitions{} - for k, v := range orgDefinitions { - if usedDefinitions[k] { - sp.Definitions[k] = v - } - } -} - -func equalSchemaMap(s1, s2 map[string]spec.Schema) bool { - if len(s1) != len(s2) { - return false - } - for k, v := range s1 { - v2, found := s2[k] - if !found { - return false - } - if !EqualSchema(&v, &v2) { - return false - } - } - return true -} - -func equalSchemaArray(s1, s2 []spec.Schema) bool { - if s1 == nil || s2 == nil { - return s1 == nil && s2 == nil - } - if len(s1) != len(s2) { - return false - } - for _, v1 := range s1 { - found := false - for _, v2 := range s2 { - if EqualSchema(&v1, &v2) { - found = true - break - } - } - if !found { - return false - } - } - for _, v2 := range s2 { - found := false - for _, v1 := range s1 { - if EqualSchema(&v1, &v2) { - found = true - break - } - } - if !found { - return false - } - } - return true -} - -func equalSchemaOrBool(s1, s2 *spec.SchemaOrBool) bool { - if s1 == nil || s2 == nil { - return s1 == s2 - } - if s1.Allows != s2.Allows { - return false - } - if !EqualSchema(s1.Schema, s2.Schema) { - return false - } - return true -} - -func equalSchemaOrArray(s1, s2 *spec.SchemaOrArray) bool { - if s1 == nil || s2 == nil { - return s1 == s2 - } - if !EqualSchema(s1.Schema, s2.Schema) { - return false - } - if !equalSchemaArray(s1.Schemas, s2.Schemas) { - return false - } - return true -} - -func equalStringArray(s1, s2 []string) bool { - if len(s1) != len(s2) { - return false - } - for _, v1 := range s1 { - found := false - for _, v2 := range s2 { - if v1 == v2 { - found = true - break - } - } - if !found { - return false - } - } - for _, v2 := range s2 { - found := false - for _, v1 := range s1 { - if v1 == v2 { - found = true - break - } - } - if !found { - return false - } - } - return true -} - -func equalFloatPointer(s1, s2 *float64) bool { - if s1 == nil || s2 == nil { - return s1 == s2 - } - return *s1 == *s2 -} - -func equalIntPointer(s1, s2 *int64) bool { - if s1 == nil || s2 == nil { - return s1 == s2 - } - return *s1 == *s2 -} - -// EqualSchema returns true if models have the same properties and references -// even if they have different documentation. -func EqualSchema(s1, s2 *spec.Schema) bool { - if s1 == nil || s2 == nil { - return s1 == s2 - } - if s1.Ref.String() != s2.Ref.String() { - return false - } - if !equalSchemaMap(s1.Definitions, s2.Definitions) { - return false - } - if !equalSchemaMap(s1.Properties, s2.Properties) { - fmt.Println("Not equal props") - return false - } - if !equalSchemaMap(s1.PatternProperties, s2.PatternProperties) { - return false - } - if !equalSchemaArray(s1.AllOf, s2.AllOf) { - return false - } - if !equalSchemaArray(s1.AnyOf, s2.AnyOf) { - return false - } - if !equalSchemaArray(s1.OneOf, s2.OneOf) { - return false - } - if !EqualSchema(s1.Not, s2.Not) { - return false - } - if !equalSchemaOrBool(s1.AdditionalProperties, s2.AdditionalProperties) { - return false - } - if !equalSchemaOrBool(s1.AdditionalItems, s2.AdditionalItems) { - return false - } - if !equalSchemaOrArray(s1.Items, s2.Items) { - return false - } - if !equalStringArray(s1.Type, s2.Type) { - return false - } - if s1.Format != s2.Format { - return false - } - if !equalFloatPointer(s1.Minimum, s2.Minimum) { - return false - } - if !equalFloatPointer(s1.Maximum, s2.Maximum) { - return false - } - if s1.ExclusiveMaximum != s2.ExclusiveMaximum { - return false - } - if s1.ExclusiveMinimum != s2.ExclusiveMinimum { - return false - } - if !equalFloatPointer(s1.MultipleOf, s2.MultipleOf) { - return false - } - if !equalIntPointer(s1.MaxLength, s2.MaxLength) { - return false - } - if !equalIntPointer(s1.MinLength, s2.MinLength) { - return false - } - if !equalIntPointer(s1.MaxItems, s2.MaxItems) { - return false - } - if !equalIntPointer(s1.MinItems, s2.MinItems) { - return false - } - if s1.Pattern != s2.Pattern { - return false - } - if s1.UniqueItems != s2.UniqueItems { - return false - } - if !equalIntPointer(s1.MaxProperties, s2.MaxProperties) { - return false - } - if !equalIntPointer(s1.MinProperties, s2.MinProperties) { - return false - } - if !equalStringArray(s1.Required, s2.Required) { - return false - } - return len(s1.Enum) == 0 && len(s2.Enum) == 0 && len(s1.Dependencies) == 0 && len(s2.Dependencies) == 0 -} - -func renameDefinition(s *spec.Swagger, old, new string) { - old_ref := DEFINITION_PREFIX + old - new_ref := DEFINITION_PREFIX + new - newWalkAllRefs(func(ref spec.Ref) spec.Ref { - if ref.String() == old_ref { - return spec.MustCreateRef(new_ref) - } - return ref - }, s).Start() - s.Definitions[new] = s.Definitions[old] - delete(s.Definitions, old) -} - -// Copy paths and definitions from source to dest, rename definitions if needed. -// dest will be mutated, and source will not be changed. -func MergeSpecs(dest, source *spec.Swagger) error { - source, err := CloneSpec(source) - if err != nil { - return err - } - for k, v := range source.Paths.Paths { - if _, found := dest.Paths.Paths[k]; found { - return fmt.Errorf("Unable to merge: Duplicated path %s", k) - } - dest.Paths.Paths[k] = v - } - usedNames := map[string]bool{} - for k := range dest.Definitions { - usedNames[k] = true - } - type Rename struct { - from, to string - } - renames := []Rename{} - for k, v := range source.Definitions { - v2, found := dest.Definitions[k] - if found || usedNames[k] { - if found && EqualSchema(&v, &v2) { - continue - } - i := 2 - newName := fmt.Sprintf("%s_v%d", k, i) - for usedNames[newName] { - i += 1 - newName = fmt.Sprintf("%s_v%d", k, i) - } - renames = append(renames, Rename{from: k, to: newName}) - usedNames[newName] = true - } else { - usedNames[k] = true - } - } - for _, r := range renames { - renameDefinition(source, r.from, r.to) - } - for k, v := range source.Definitions { - if _, found := dest.Definitions[k]; !found { - dest.Definitions[k] = v - } - } - return nil -} - -// Clone OpenAPI spec -func CloneSpec(source *spec.Swagger) (*spec.Swagger, error) { - if ret, err := cloner.DeepCopy(source); err != nil { - return nil, err - } else { - return ret.(*spec.Swagger), nil - } -} diff --git a/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_aggregator_test.go b/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_aggregator_test.go deleted file mode 100644 index 2d6d86618f6..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/server/openapi/openapi_aggregator_test.go +++ /dev/null @@ -1,520 +0,0 @@ -/* -Copyright 2017 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 ( - "testing" - - "github.com/ghodss/yaml" - "github.com/go-openapi/spec" - "github.com/stretchr/testify/assert" -) - -func TestFilterSpecs(t *testing.T) { - var spec1, spec1_filtered *spec.Swagger - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /test: - post: - tags: - - "test" - summary: "Test API" - operationId: "addTest" - parameters: - - in: "body" - name: "body" - description: "test object" - required: true - schema: - $ref: "#/definitions/Test" - responses: - 405: - description: "Invalid input" - $ref: "#/definitions/InvalidInput" - /othertest: - post: - tags: - - "test2" - summary: "Test2 API" - operationId: "addTest2" - consumes: - - "application/json" - produces: - - "application/xml" - parameters: - - in: "body" - name: "body" - description: "test2 object" - required: true - schema: - $ref: "#/definitions/Test2" -definitions: - Test: - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "Status" - InvalidInput: - type: "string" - format: "string" - Test2: - type: "object" - properties: - other: - $ref: "#/definitions/Other" - Other: - type: "string" -`), &spec1) - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /test: - post: - tags: - - "test" - summary: "Test API" - operationId: "addTest" - parameters: - - in: "body" - name: "body" - description: "test object" - required: true - schema: - $ref: "#/definitions/Test" - responses: - 405: - description: "Invalid input" - $ref: "#/definitions/InvalidInput" -definitions: - Test: - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "Status" - InvalidInput: - type: "string" - format: "string" -`), &spec1_filtered) - assert := assert.New(t) - FilterSpecByPaths(spec1, []string{"/test"}) - assert.Equal(spec1_filtered, spec1) -} - -func TestMergeSpecsSimple(t *testing.T) { - var spec1, spec2, expected *spec.Swagger - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /test: - post: - tags: - - "test" - summary: "Test API" - operationId: "addTest" - parameters: - - in: "body" - name: "body" - description: "test object" - required: true - schema: - $ref: "#/definitions/Test" - responses: - 405: - description: "Invalid input" - $ref: "#/definitions/InvalidInput" -definitions: - Test: - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "Status" - InvalidInput: - type: "string" - format: "string" -`), &spec1) - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /othertest: - post: - tags: - - "test2" - summary: "Test2 API" - operationId: "addTest2" - consumes: - - "application/json" - produces: - - "application/xml" - parameters: - - in: "body" - name: "body" - description: "test2 object" - required: true - schema: - $ref: "#/definitions/Test2" -definitions: - Test2: - type: "object" - properties: - other: - $ref: "#/definitions/Other" - Other: - type: "string" -`), &spec2) - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /test: - post: - tags: - - "test" - summary: "Test API" - operationId: "addTest" - parameters: - - in: "body" - name: "body" - description: "test object" - required: true - schema: - $ref: "#/definitions/Test" - responses: - 405: - description: "Invalid input" - $ref: "#/definitions/InvalidInput" - /othertest: - post: - tags: - - "test2" - summary: "Test2 API" - operationId: "addTest2" - consumes: - - "application/json" - produces: - - "application/xml" - parameters: - - in: "body" - name: "body" - description: "test2 object" - required: true - schema: - $ref: "#/definitions/Test2" -definitions: - Test: - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "Status" - InvalidInput: - type: "string" - format: "string" - Test2: - type: "object" - properties: - other: - $ref: "#/definitions/Other" - Other: - type: "string" -`), &expected) - assert := assert.New(t) - if !assert.NoError(MergeSpecs(spec1, spec2)) { - return - } - assert.Equal(expected, spec1) -} - -func TestMergeSpecsReuseModel(t *testing.T) { - var spec1, spec2, expected *spec.Swagger - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /test: - post: - tags: - - "test" - summary: "Test API" - operationId: "addTest" - parameters: - - in: "body" - name: "body" - description: "test object" - required: true - schema: - $ref: "#/definitions/Test" - responses: - 405: - description: "Invalid input" - $ref: "#/definitions/InvalidInput" -definitions: - Test: - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "Status" - InvalidInput: - type: "string" - format: "string" -`), &spec1) - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /othertest: - post: - tags: - - "test2" - summary: "Test2 API" - operationId: "addTest2" - consumes: - - "application/json" - produces: - - "application/xml" - parameters: - - in: "body" - name: "body" - description: "test2 object" - required: true - schema: - $ref: "#/definitions/Test" -definitions: - Test: - description: "This Test has a description" - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "This status has another description" - InvalidInput: - type: "string" - format: "string" -`), &spec2) - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /test: - post: - tags: - - "test" - summary: "Test API" - operationId: "addTest" - parameters: - - in: "body" - name: "body" - description: "test object" - required: true - schema: - $ref: "#/definitions/Test" - responses: - 405: - description: "Invalid input" - $ref: "#/definitions/InvalidInput" - /othertest: - post: - tags: - - "test2" - summary: "Test2 API" - operationId: "addTest2" - consumes: - - "application/json" - produces: - - "application/xml" - parameters: - - in: "body" - name: "body" - description: "test2 object" - required: true - schema: - $ref: "#/definitions/Test" -definitions: - Test: - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "Status" - InvalidInput: - type: "string" - format: "string" -`), &expected) - assert := assert.New(t) - if !assert.NoError(MergeSpecs(spec1, spec2)) { - return - } - assert.Equal(expected, spec1) -} - -func TestMergeSpecsRenameModel(t *testing.T) { - var spec1, spec2, expected *spec.Swagger - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /test: - post: - tags: - - "test" - summary: "Test API" - operationId: "addTest" - parameters: - - in: "body" - name: "body" - description: "test object" - required: true - schema: - $ref: "#/definitions/Test" - responses: - 405: - description: "Invalid input" - $ref: "#/definitions/InvalidInput" -definitions: - Test: - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "Status" - InvalidInput: - type: "string" - format: "string" -`), &spec1) - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /othertest: - post: - tags: - - "test2" - summary: "Test2 API" - operationId: "addTest2" - consumes: - - "application/json" - produces: - - "application/xml" - parameters: - - in: "body" - name: "body" - description: "test2 object" - required: true - schema: - $ref: "#/definitions/Test" -definitions: - Test: - description: "This Test has a description" - type: "object" - properties: - id: - type: "integer" - format: "int64" - InvalidInput: - type: "string" - format: "string" -`), &spec2) - yaml.Unmarshal([]byte(` -swagger: "2.0" -paths: - /test: - post: - tags: - - "test" - summary: "Test API" - operationId: "addTest" - parameters: - - in: "body" - name: "body" - description: "test object" - required: true - schema: - $ref: "#/definitions/Test" - responses: - 405: - description: "Invalid input" - $ref: "#/definitions/InvalidInput" - /othertest: - post: - tags: - - "test2" - summary: "Test2 API" - operationId: "addTest2" - consumes: - - "application/json" - produces: - - "application/xml" - parameters: - - in: "body" - name: "body" - description: "test2 object" - required: true - schema: - $ref: "#/definitions/Test_v2" -definitions: - Test: - type: "object" - properties: - id: - type: "integer" - format: "int64" - status: - type: "string" - description: "Status" - Test_v2: - description: "This Test has a description" - type: "object" - properties: - id: - type: "integer" - format: "int64" - InvalidInput: - type: "string" - format: "string" -`), &expected) - assert := assert.New(t) - if !assert.NoError(MergeSpecs(spec1, spec2)) { - return - } - - expected_yaml, _ := yaml.Marshal(expected) - spec1_yaml, _ := yaml.Marshal(spec1) - - assert.Equal(string(expected_yaml), string(spec1_yaml)) -} diff --git a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go index 5e07d36ecbf..06ab7462a3b 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go @@ -17,10 +17,7 @@ limitations under the License. package apiserver import ( - "context" - "encoding/json" "net/http" - "sync" "time" "k8s.io/apimachinery/pkg/apimachinery/announced" @@ -36,15 +33,6 @@ import ( kubeinformers "k8s.io/client-go/informers" "k8s.io/client-go/pkg/version" - "bytes" - "fmt" - "io" - - "github.com/go-openapi/spec" - "github.com/golang/glog" - "github.com/pkg/errors" - "k8s.io/apiserver/pkg/server/openapi" - "k8s.io/client-go/transport" "k8s.io/kube-aggregator/pkg/apis/apiregistration" "k8s.io/kube-aggregator/pkg/apis/apiregistration/install" "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" @@ -62,10 +50,6 @@ var ( Codecs = serializer.NewCodecFactory(Scheme) ) -const ( - LOAD_OPENAPI_SPEC_MAX_RETRIES = 10 -) - func init() { install.Install(groupFactoryRegistry, registry, Scheme) @@ -123,24 +107,6 @@ type APIAggregator struct { // handledGroups are the groups that already have routes handledGroups sets.String - // Swagger spec for each api service - apiServiceSpecs map[string]*spec.Swagger - - // List of the specs that needs to be loaded. When a spec is successfully loaded - // it will be removed from this list and added to apiServiceSpecs. - // Map values are retry counts. After a preset retries, it will stop - // trying. - toLoadAPISpec map[string]int - - // protecting toLoadAPISpec and apiServiceSpecs - specMutex sync.Mutex - - // rootSpec is the OpenAPI spec of the Aggregator server. - rootSpec *spec.Swagger - - // delegationSpec is the delegation API Server's spec (most of API groups are in this spec). - delegationSpec *spec.Swagger - // lister is used to add group handling for /apis/ aggregator lookups based on // controller state lister listers.APIServiceLister @@ -193,14 +159,11 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg s := &APIAggregator{ GenericAPIServer: genericServer, delegateHandler: delegationTarget.UnprotectedHandler(), - delegationSpec: delegationTarget.OpenAPISpec(), contextMapper: c.GenericConfig.RequestContextMapper, proxyClientCert: c.ProxyClientCert, proxyClientKey: c.ProxyClientKey, proxyTransport: c.ProxyTransport, proxyHandlers: map[string]*proxyHandler{}, - apiServiceSpecs: map[string]*spec.Swagger{}, - toLoadAPISpec: map[string]int{}, handledGroups: sets.String{}, lister: informerFactory.Apiregistration().InternalVersion().APIServices().Lister(), APIRegistrationInformers: informerFactory, @@ -249,20 +212,6 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg return nil }) - s.GenericAPIServer.PrepareOpenAPIService() - - if s.GenericAPIServer.OpenAPIService != nil { - s.rootSpec = s.GenericAPIServer.OpenAPIService.GetSpec() - if err := s.updateOpenAPISpec(); err != nil { - return nil, err - } - s.GenericAPIServer.OpenAPIService.AddUpdateHook(func(r *http.Request) { - if s.tryLoadingOpenAPISpecs(r) { - s.updateOpenAPISpec() - } - }) - } - return s, nil } @@ -293,9 +242,6 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) { } proxyHandler.updateAPIService(apiService) s.proxyHandlers[apiService.Name] = proxyHandler - - s.deferLoadAPISpec(apiService.Name) - s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler) s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler) @@ -324,19 +270,6 @@ func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) { s.handledGroups.Insert(apiService.Spec.Group) } -func (s *APIAggregator) deferLoadAPISpec(name string) { - s.specMutex.Lock() - defer s.specMutex.Unlock() - s.toLoadAPISpec[name] = 0 -} - -func (s *APIAggregator) deleteApiSpec(name string) { - s.specMutex.Lock() - defer s.specMutex.Unlock() - delete(s.apiServiceSpecs, name) - delete(s.toLoadAPISpec, name) -} - // RemoveAPIService removes the APIService from being handled. It is not thread-safe, so only call it on one thread at a time please. // It's a slow moving API, so its ok to run the controller on a single thread. func (s *APIAggregator) RemoveAPIService(apiServiceName string) { @@ -350,124 +283,7 @@ func (s *APIAggregator) RemoveAPIService(apiServiceName string) { s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath) s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/") delete(s.proxyHandlers, apiServiceName) - s.deleteApiSpec(apiServiceName) - s.updateOpenAPISpec() // TODO unregister group level discovery when there are no more versions for the group // We don't need this right away because the handler properly delegates when no versions are present } - -func (_ *APIAggregator) loadOpenAPISpec(p *proxyHandler, r *http.Request) (*spec.Swagger, error) { - value := p.handlingInfo.Load() - if value == nil { - return nil, nil - } - handlingInfo := value.(proxyHandlingInfo) - if handlingInfo.local { - return nil, nil - } - loc, err := p.serviceResolver.ResolveEndpoint(handlingInfo.serviceNamespace, handlingInfo.serviceName) - if err != nil { - return nil, fmt.Errorf("missing route") - } - host := loc.Host - - var w io.Reader - req, err := http.NewRequest("GET", "/swagger.json", w) - if err != nil { - return nil, err - } - req.URL.Scheme = "https" - req.URL.Host = host - - req = req.WithContext(context.Background()) - // Get user from the original request - ctx, ok := p.contextMapper.Get(r) - if !ok { - return nil, fmt.Errorf("missing context") - } - user, ok := genericapirequest.UserFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing user") - } - proxyRoundTripper := transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), handlingInfo.proxyRoundTripper) - res, err := proxyRoundTripper.RoundTrip(req) - if err != nil { - return nil, err - } - if res.StatusCode != http.StatusOK { - return nil, errors.New(res.Status) - } - buf := new(bytes.Buffer) - buf.ReadFrom(res.Body) - bytes := buf.Bytes() - var s spec.Swagger - if err := json.Unmarshal(bytes, &s); err != nil { - return nil, err - } - return &s, nil -} - -// Returns true if any Spec is loaded -func (s *APIAggregator) tryLoadingOpenAPISpecs(r *http.Request) bool { - s.specMutex.Lock() - defer s.specMutex.Unlock() - if len(s.toLoadAPISpec) == 0 { - return false - } - loaded := false - newList := map[string]int{} - for name, retries := range s.toLoadAPISpec { - if retries >= LOAD_OPENAPI_SPEC_MAX_RETRIES { - continue - } - proxyHandler := s.proxyHandlers[name] - if spec, err := s.loadOpenAPISpec(proxyHandler, r); err != nil { - glog.Warningf("Failed to Load OpenAPI spec (try %d of %d) for %s, err=%s", retries+1, LOAD_OPENAPI_SPEC_MAX_RETRIES, name, err) - newList[name] = retries + 1 - } else if spec != nil { - s.apiServiceSpecs[name] = spec - loaded = true - } - s.toLoadAPISpec = newList - } - return loaded -} - -func (s *APIAggregator) updateOpenAPISpec() error { - s.specMutex.Lock() - defer s.specMutex.Unlock() - if s.GenericAPIServer.OpenAPIService == nil { - return nil - } - sp, err := openapi.CloneSpec(s.rootSpec) - if err != nil { - return err - } - openapi.FilterSpecByPaths(sp, []string{"/apis/apiregistration.k8s.io/"}) - if _, found := sp.Paths.Paths["/version/"]; found { - return fmt.Errorf("Cleanup didn't work") - } - if err := openapi.MergeSpecs(sp, s.delegationSpec); err != nil { - return err - } - - for k, v := range s.apiServiceSpecs { - version := apiregistration.APIServiceNameToGroupVersion(k) - - proxyPath := "/apis/" + version.Group + "/" - // v1. is a special case for the legacy API. It proxies to a wider set of endpoints. - if k == legacyAPIServiceName { - proxyPath = "/api/" - } - spc, err := openapi.CloneSpec(v) - if err != nil { - return err - } - openapi.FilterSpecByPaths(spc, []string{proxyPath}) - if err := openapi.MergeSpecs(sp, spc); err != nil { - return err - } - } - return s.GenericAPIServer.OpenAPIService.UpdateSpec(sp) -}