mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-04-22 22:58:08 -04:00
graduate config.k8s.io.v1alpha1.flagz to beta
This commit is contained in:
parent
4dd6454b02
commit
adbf7dee82
14 changed files with 660 additions and 190 deletions
58
pkg/generated/openapi/zz_generated.openapi.go
generated
58
pkg/generated/openapi/zz_generated.openapi.go
generated
|
|
@ -89,8 +89,9 @@ import (
|
|||
version "k8s.io/apimachinery/pkg/version"
|
||||
auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
|
||||
apiv1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
apiv1beta1 "k8s.io/apiserver/pkg/server/flagz/api/v1beta1"
|
||||
statuszapiv1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1"
|
||||
apiv1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1"
|
||||
statuszapiv1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1"
|
||||
clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
configv1alpha1 "k8s.io/cloud-provider/config/v1alpha1"
|
||||
|
|
@ -1318,8 +1319,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
|||
auditv1.PolicyList{}.OpenAPIModelName(): schema_pkg_apis_audit_v1_PolicyList(ref),
|
||||
auditv1.PolicyRule{}.OpenAPIModelName(): schema_pkg_apis_audit_v1_PolicyRule(ref),
|
||||
apiv1alpha1.Flagz{}.OpenAPIModelName(): schema_server_flagz_api_v1alpha1_Flagz(ref),
|
||||
apiv1beta1.Flagz{}.OpenAPIModelName(): schema_server_flagz_api_v1beta1_Flagz(ref),
|
||||
statuszapiv1alpha1.Statusz{}.OpenAPIModelName(): schema_server_statusz_api_v1alpha1_Statusz(ref),
|
||||
apiv1beta1.Statusz{}.OpenAPIModelName(): schema_server_statusz_api_v1beta1_Statusz(ref),
|
||||
statuszapiv1beta1.Statusz{}.OpenAPIModelName(): schema_server_statusz_api_v1beta1_Statusz(ref),
|
||||
clientauthenticationv1.Cluster{}.OpenAPIModelName(): schema_pkg_apis_clientauthentication_v1_Cluster(ref),
|
||||
clientauthenticationv1.ExecCredential{}.OpenAPIModelName(): schema_pkg_apis_clientauthentication_v1_ExecCredential(ref),
|
||||
clientauthenticationv1.ExecCredentialSpec{}.OpenAPIModelName(): schema_pkg_apis_clientauthentication_v1_ExecCredentialSpec(ref),
|
||||
|
|
@ -63962,6 +63964,58 @@ func schema_server_flagz_api_v1alpha1_Flagz(ref common.ReferenceCallback) common
|
|||
}
|
||||
}
|
||||
|
||||
func schema_server_flagz_api_v1beta1_Flagz(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Flagz is the structured response for the /flagz endpoint.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Standard object's metadata.",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref(metav1.ObjectMeta{}.OpenAPIModelName()),
|
||||
},
|
||||
},
|
||||
"flags": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Flags contains the command-line flags and their values. The keys are the flag names and the values are the flag values, possibly with confidential values redacted.",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
metav1.ObjectMeta{}.OpenAPIModelName()},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_server_statusz_api_v1alpha1_Statusz(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
|
|
|||
|
|
@ -18,5 +18,5 @@ limitations under the License.
|
|||
// +k8s:openapi-gen=true
|
||||
// +k8s:openapi-model-package=io.k8s.apiserver.pkg.server.flagz.api.v1alpha1
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the zpages v1alpha1 API group
|
||||
// Package v1alpha1 contains API Schema definitions for the flagz v1alpha1 API group
|
||||
package v1alpha1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:openapi-model-package=io.k8s.apiserver.pkg.server.flagz.api.v1beta1
|
||||
|
||||
// Package v1beta1 contains API Schema definitions for the flagz v1beta1 API group
|
||||
package v1beta1
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Copyright 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 v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
GroupName = "config.k8s.io"
|
||||
Version = "v1beta1"
|
||||
)
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version}
|
||||
|
||||
var (
|
||||
// SchemeBuilder initializes a scheme builder
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
// AddToScheme is a global function that adds this group's types to a scheme
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Flagz{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
Copyright 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 v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// Flagz is the structured response for the /flagz endpoint.
|
||||
type Flagz struct {
|
||||
// TypeMeta is the type metadata for the object.
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object's metadata.
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Flags contains the command-line flags and their values.
|
||||
// The keys are the flag names and the values are the flag values,
|
||||
// possibly with confidential values redacted.
|
||||
// +optional
|
||||
Flags map[string]string `json:"flags,omitempty"`
|
||||
}
|
||||
59
staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.deepcopy.go
generated
Normal file
59
staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.deepcopy.go
generated
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Flagz) DeepCopyInto(out *Flagz) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
if in.Flags != nil {
|
||||
in, out := &in.Flags, &out.Flags
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Flagz.
|
||||
func (in *Flagz) DeepCopy() *Flagz {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Flagz)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Flagz) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
27
staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.model_name.go
generated
Normal file
27
staging/src/k8s.io/apiserver/pkg/server/flagz/api/v1beta1/zz_generated.model_name.go
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by openapi-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
// OpenAPIModelName returns the OpenAPI model name for this type.
|
||||
func (in Flagz) OpenAPIModelName() string {
|
||||
return "io.k8s.apiserver.pkg.server.flagz.api.v1beta1.Flagz"
|
||||
}
|
||||
|
|
@ -34,18 +34,23 @@ import (
|
|||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
v1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/server/flagz/api/v1beta1"
|
||||
"k8s.io/apiserver/pkg/server/flagz/negotiate"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultFlagzPath = "/flagz"
|
||||
Kind = "Flagz"
|
||||
GroupName = "config.k8s.io"
|
||||
Version = "v1alpha1"
|
||||
var (
|
||||
v1alpha1FlagzKind = v1alpha1.SchemeGroupVersion.WithKind("Flagz")
|
||||
v1beta1FlagzKind = v1beta1.SchemeGroupVersion.WithKind("Flagz")
|
||||
recognizedStructuredKinds = map[schema.GroupVersionKind]bool{
|
||||
v1alpha1FlagzKind: true,
|
||||
v1beta1FlagzKind: true,
|
||||
}
|
||||
)
|
||||
|
||||
const DefaultFlagzPath = "/flagz"
|
||||
|
||||
// flagzCodecFactory wraps a CodecFactory to filter out unsupported media types (like protobuf)
|
||||
// from the supported media types list, so error messages only show actually supported types.
|
||||
type flagzCodecFactory struct {
|
||||
|
|
@ -61,7 +66,7 @@ type mux interface {
|
|||
func Install(m mux, componentName string, flagReader Reader, opts ...Option) {
|
||||
reg := ®istry{
|
||||
reader: flagReader,
|
||||
deprecatedVersionsMap: map[string]bool{},
|
||||
deprecatedVersionsMap: map[string]bool{"v1alpha1": true},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(reg)
|
||||
|
|
@ -69,11 +74,15 @@ func Install(m mux, componentName string, flagReader Reader, opts ...Option) {
|
|||
|
||||
scheme := runtime.NewScheme()
|
||||
utilruntime.Must(v1alpha1.AddToScheme(scheme))
|
||||
utilruntime.Must(v1beta1.AddToScheme(scheme))
|
||||
filteredCodecFactory, err := newFlagzCodecFactory(scheme, componentName, reg.reader)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
m.Handle(DefaultFlagzPath, handleFlagz(componentName, reg, filteredCodecFactory, negotiate.FlagzEndpointRestrictions{}))
|
||||
restrictions := negotiate.FlagzEndpointRestrictions{
|
||||
RecognizedStructuredKinds: recognizedStructuredKinds,
|
||||
}
|
||||
m.Handle(DefaultFlagzPath, handleFlagz(componentName, reg, filteredCodecFactory, restrictions))
|
||||
}
|
||||
|
||||
// newFlagzCodecFactory creates a codec factory with the standard serializers for flagz,
|
||||
|
|
@ -157,10 +166,9 @@ func handleFlagz(componentName string, reg *registry, serializer runtime.Negotia
|
|||
delegate.Status(), delegate.ContentLength(), time.Since(requestReceivedTimestamp))
|
||||
}()
|
||||
|
||||
obj := flagz(componentName, reg.reader)
|
||||
acceptHeader := r.Header.Get("Accept")
|
||||
if strings.TrimSpace(acceptHeader) == "" {
|
||||
writePlainTextResponse(obj, serializer, w, reg)
|
||||
writePlainTextResponse(v1beta1Flagz(componentName, reg.reader), serializer, w, reg)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +185,6 @@ func handleFlagz(componentName string, reg *registry, serializer runtime.Negotia
|
|||
return
|
||||
}
|
||||
|
||||
var targetGV schema.GroupVersion
|
||||
switch serializerInfo.MediaType {
|
||||
case "application/json", "application/yaml", "application/cbor":
|
||||
if mediaType.Convert == nil {
|
||||
|
|
@ -192,18 +199,15 @@ func handleFlagz(componentName string, reg *registry, serializer runtime.Negotia
|
|||
)
|
||||
return
|
||||
}
|
||||
// Set group, version, and deprecated from the negotiated target so
|
||||
// the deferred MonitorRequest records the actual requested API version.
|
||||
targetGV = mediaType.Convert.GroupVersion()
|
||||
group = targetGV.Group
|
||||
version = targetGV.Version
|
||||
deprecated = reg.deprecatedVersions()[targetGV.Version]
|
||||
group = mediaType.Convert.Group
|
||||
version = mediaType.Convert.Version
|
||||
deprecated = reg.deprecatedVersions()[version]
|
||||
if deprecated {
|
||||
w.Header().Set("Warning", `299 - "This version of the flagz endpoint is deprecated. Please use a newer version."`)
|
||||
}
|
||||
writeStructuredResponse(obj, serializer, targetGV, restrictions, w, r)
|
||||
handleStructuredResponse(w, r, componentName, reg, serializer, restrictions, mediaType)
|
||||
case "text/plain":
|
||||
writePlainTextResponse(obj, serializer, w, reg)
|
||||
writePlainTextResponse(v1beta1Flagz(componentName, reg.reader), serializer, w, reg)
|
||||
default:
|
||||
err := fmt.Errorf("unsupported media type: %s/%s", serializerInfo.MediaType, serializerInfo.MediaTypeSubType)
|
||||
utilruntime.HandleError(err)
|
||||
|
|
@ -268,12 +272,45 @@ func writeStructuredResponse(obj runtime.Object, serializer runtime.NegotiatedSe
|
|||
)
|
||||
}
|
||||
|
||||
func flagz(componentName string, flagReader Reader) *v1alpha1.Flagz {
|
||||
func handleStructuredResponse(w http.ResponseWriter, r *http.Request, componentName string, reg *registry, serializer runtime.NegotiatedSerializer, restrictions negotiate.FlagzEndpointRestrictions, mediaType negotiation.MediaTypeOptions) {
|
||||
switch *mediaType.Convert {
|
||||
case v1alpha1FlagzKind:
|
||||
writeStructuredResponse(v1alpha1Flagz(componentName, reg.reader), serializer, v1alpha1FlagzKind.GroupVersion(), restrictions, w, r)
|
||||
case v1beta1FlagzKind:
|
||||
writeStructuredResponse(v1beta1Flagz(componentName, reg.reader), serializer, v1beta1FlagzKind.GroupVersion(), restrictions, w, r)
|
||||
default:
|
||||
err := fmt.Errorf("unsupported media type: %s", mediaType.Convert.String())
|
||||
utilruntime.HandleError(err)
|
||||
responsewriters.ErrorNegotiated(
|
||||
err,
|
||||
serializer,
|
||||
schema.GroupVersion{},
|
||||
w,
|
||||
r,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func v1alpha1Flagz(componentName string, flagReader Reader) *v1alpha1.Flagz {
|
||||
flags := flagReader.GetFlagz()
|
||||
return &v1alpha1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: Kind,
|
||||
APIVersion: fmt.Sprintf("%s/%s", GroupName, Version),
|
||||
Kind: v1alpha1FlagzKind.Kind,
|
||||
APIVersion: v1alpha1FlagzKind.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: componentName,
|
||||
},
|
||||
Flags: flags,
|
||||
}
|
||||
}
|
||||
|
||||
func v1beta1Flagz(componentName string, flagReader Reader) *v1beta1.Flagz {
|
||||
flags := flagReader.GetFlagz()
|
||||
return &v1beta1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: v1beta1FlagzKind.Kind,
|
||||
APIVersion: v1beta1FlagzKind.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: componentName,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ import (
|
|||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
v1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/server/flagz/api/v1beta1"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
|
|
@ -68,7 +69,7 @@ func TestHandleFlagz(t *testing.T) {
|
|||
registry *registry
|
||||
wantStatusCode int
|
||||
wantBody string
|
||||
wantStructuredBody *v1alpha1.Flagz
|
||||
wantStructuredBody interface{}
|
||||
wantWarning bool
|
||||
}{
|
||||
{
|
||||
|
|
@ -87,17 +88,17 @@ func TestHandleFlagz(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "valid request for application/json",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
componentName: "test-server",
|
||||
registry: ®istry{
|
||||
reader: fakeReader,
|
||||
deprecatedVersionsMap: map[string]bool{},
|
||||
},
|
||||
wantStatusCode: http.StatusOK,
|
||||
wantStructuredBody: &v1alpha1.Flagz{
|
||||
wantStructuredBody: &v1beta1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: Kind,
|
||||
APIVersion: fmt.Sprintf("%s/%s", GroupName, Version),
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-server",
|
||||
|
|
@ -118,8 +119,8 @@ func TestHandleFlagz(t *testing.T) {
|
|||
wantStatusCode: http.StatusOK,
|
||||
wantStructuredBody: &v1alpha1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: Kind,
|
||||
APIVersion: fmt.Sprintf("%s/%s", GroupName, Version),
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-server",
|
||||
|
|
@ -132,17 +133,17 @@ func TestHandleFlagz(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "valid request for application/yaml",
|
||||
acceptHeader: "application/yaml;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
acceptHeader: "application/yaml;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
componentName: "test-server",
|
||||
registry: ®istry{
|
||||
reader: fakeReader,
|
||||
deprecatedVersionsMap: map[string]bool{},
|
||||
},
|
||||
wantStatusCode: http.StatusOK,
|
||||
wantStructuredBody: &v1alpha1.Flagz{
|
||||
wantStructuredBody: &v1beta1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: Kind,
|
||||
APIVersion: fmt.Sprintf("%s/%s", GroupName, Version),
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-server",
|
||||
|
|
@ -154,17 +155,17 @@ func TestHandleFlagz(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "valid request for application/cbor",
|
||||
acceptHeader: "application/cbor;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
acceptHeader: "application/cbor;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
componentName: "test-server",
|
||||
registry: ®istry{
|
||||
reader: fakeReader,
|
||||
deprecatedVersionsMap: map[string]bool{},
|
||||
},
|
||||
wantStatusCode: http.StatusOK,
|
||||
wantStructuredBody: &v1alpha1.Flagz{
|
||||
wantStructuredBody: &v1beta1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: Kind,
|
||||
APIVersion: fmt.Sprintf("%s/%s", GroupName, Version),
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-server",
|
||||
|
|
@ -238,7 +239,7 @@ func TestHandleFlagz(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "unsupported application/json with missing params",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io",
|
||||
componentName: "test-server",
|
||||
registry: ®istry{
|
||||
reader: fakeReader,
|
||||
|
|
@ -281,9 +282,18 @@ func TestHandleFlagz(t *testing.T) {
|
|||
|
||||
if tt.wantStatusCode == http.StatusOK {
|
||||
if tt.wantStructuredBody != nil {
|
||||
var got v1alpha1.Flagz
|
||||
unmarshalResponse(t, w.Header().Get("Content-Type"), w.Body.Bytes(), &got)
|
||||
if diff := cmp.Diff(*tt.wantStructuredBody, got); diff != "" {
|
||||
var got interface{}
|
||||
switch tt.wantStructuredBody.(type) {
|
||||
case *v1alpha1.Flagz:
|
||||
got = &v1alpha1.Flagz{}
|
||||
case *v1beta1.Flagz:
|
||||
got = &v1beta1.Flagz{}
|
||||
default:
|
||||
t.Fatalf("unexpected type for wantStructuredBody: %T", tt.wantStructuredBody)
|
||||
}
|
||||
unmarshalResponse(t, w.Header().Get("Content-Type"), w.Body.Bytes(), got)
|
||||
|
||||
if diff := cmp.Diff(tt.wantStructuredBody, got); diff != "" {
|
||||
t.Errorf("Unexpected diff on response (-want,+got):\n%s", diff)
|
||||
}
|
||||
if tt.wantWarning {
|
||||
|
|
@ -299,7 +309,7 @@ func TestHandleFlagz(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func unmarshalResponse(t *testing.T, contentType string, body []byte, got *v1alpha1.Flagz) {
|
||||
func unmarshalResponse(t *testing.T, contentType string, body []byte, got interface{}) {
|
||||
t.Helper()
|
||||
switch {
|
||||
case strings.Contains(contentType, "application/json"):
|
||||
|
|
@ -374,6 +384,7 @@ func TestNewFlagzCodecFactory(t *testing.T) {
|
|||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CBORServingAndStorage, true)
|
||||
scheme := runtime.NewScheme()
|
||||
utilruntime.Must(v1alpha1.AddToScheme(scheme))
|
||||
utilruntime.Must(v1beta1.AddToScheme(scheme))
|
||||
|
||||
_, err := newFlagzCodecFactory(scheme, "", nil)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -20,16 +20,21 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// FlagzEndpointRestrictions implements content negotiation restrictions for the z-pages.
|
||||
// FlagzEndpointRestrictions implements content negotiation restrictions for the flagz endpoint.
|
||||
// It is used to validate and restrict which GroupVersionKinds are allowed for structured responses.
|
||||
type FlagzEndpointRestrictions struct{}
|
||||
type FlagzEndpointRestrictions struct {
|
||||
RecognizedStructuredKinds map[schema.GroupVersionKind]bool
|
||||
}
|
||||
|
||||
// AllowsMediaTypeTransform checks if the provided GVK is supported for structured z-page responses.
|
||||
func (FlagzEndpointRestrictions) AllowsMediaTypeTransform(mimeType string, mimeSubType string, gvk *schema.GroupVersionKind) bool {
|
||||
// AllowsMediaTypeTransform checks if the provided GVK is supported for structured flagz responses.
|
||||
func (f FlagzEndpointRestrictions) AllowsMediaTypeTransform(mimeType string, mimeSubType string, gvk *schema.GroupVersionKind) bool {
|
||||
if mimeType == "text" && mimeSubType == "plain" {
|
||||
return gvk == nil
|
||||
}
|
||||
return isStructured(gvk)
|
||||
if gvk != nil {
|
||||
return f.RecognizedStructuredKinds[*gvk]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (FlagzEndpointRestrictions) AllowsServerVersion(string) bool {
|
||||
|
|
@ -39,15 +44,3 @@ func (FlagzEndpointRestrictions) AllowsServerVersion(string) bool {
|
|||
func (FlagzEndpointRestrictions) AllowsStreamSchema(s string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isStructured(gvk *schema.GroupVersionKind) bool {
|
||||
if gvk != nil {
|
||||
if gvk.Group == "config.k8s.io" && gvk.Version == "v1alpha1" {
|
||||
if gvk.Kind == "Flagz" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
120
staging/src/k8s.io/apiserver/pkg/server/flagz/testing/testing.go
Normal file
120
staging/src/k8s.io/apiserver/pkg/server/flagz/testing/testing.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
Copyright 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 testing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/server/flagz/api/v1beta1"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func VerifyStructuredResponse(t *testing.T, acceptHeader string, body []byte, warnings []string, want interface{}, wantDeprecationHeader bool) {
|
||||
t.Helper()
|
||||
|
||||
unmarshal := unmarshalFunc(t, acceptHeader)
|
||||
wantTypeMeta, wantName, wantFlags := wantFields(t, want)
|
||||
gotTypeMeta, gotName, gotFlags := gotFields(t, unmarshal, body, wantTypeMeta.APIVersion)
|
||||
|
||||
if gotName != wantName {
|
||||
t.Errorf("name mismatch: got %q, want %q", gotName, wantName)
|
||||
}
|
||||
if gotTypeMeta != wantTypeMeta {
|
||||
t.Errorf("type meta mismatch: got %v, want %v", gotTypeMeta, wantTypeMeta)
|
||||
}
|
||||
for k, v := range wantFlags {
|
||||
gotV, ok := gotFlags[k]
|
||||
if !ok {
|
||||
t.Errorf("missing flag %q", k)
|
||||
continue
|
||||
}
|
||||
if gotV != v {
|
||||
t.Errorf("flag %q match: got %q, want %q", k, gotV, v)
|
||||
}
|
||||
}
|
||||
|
||||
foundWarning := false
|
||||
for _, w := range warnings {
|
||||
if strings.Contains(w, "deprecated") {
|
||||
foundWarning = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundWarning != wantDeprecationHeader {
|
||||
t.Errorf("deprecation header mismatch: got %v, want %v", foundWarning, wantDeprecationHeader)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalFunc(t *testing.T, acceptHeader string) func([]byte, interface{}) error {
|
||||
switch {
|
||||
case strings.Contains(acceptHeader, "application/json"):
|
||||
return json.Unmarshal
|
||||
case strings.Contains(acceptHeader, "application/yaml"):
|
||||
return yaml.Unmarshal
|
||||
case strings.Contains(acceptHeader, "application/cbor"):
|
||||
return cbor.Unmarshal
|
||||
default:
|
||||
t.Fatalf("unexpected Accept header: %q", acceptHeader)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func wantFields(t *testing.T, want interface{}) (metav1.TypeMeta, string, map[string]string) {
|
||||
t.Helper()
|
||||
switch w := want.(type) {
|
||||
case *v1alpha1.Flagz:
|
||||
return w.TypeMeta, w.Name, w.Flags
|
||||
case *v1beta1.Flagz:
|
||||
return w.TypeMeta, w.Name, w.Flags
|
||||
default:
|
||||
t.Fatalf("unexpected type for want: %T", want)
|
||||
return metav1.TypeMeta{}, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
func gotFields(t *testing.T, unmarshal func([]byte, interface{}) error, body []byte, apiVersion string) (metav1.TypeMeta, string, map[string]string) {
|
||||
var gotName string
|
||||
var gotTypeMeta metav1.TypeMeta
|
||||
var gotFlags map[string]string
|
||||
switch apiVersion {
|
||||
case "config.k8s.io/v1alpha1":
|
||||
var got v1alpha1.Flagz
|
||||
if err := unmarshal(body, &got); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
gotName = got.Name
|
||||
gotTypeMeta = got.TypeMeta
|
||||
gotFlags = got.Flags
|
||||
case "config.k8s.io/v1beta1":
|
||||
var got v1beta1.Flagz
|
||||
if err := unmarshal(body, &got); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
gotName = got.Name
|
||||
gotTypeMeta = got.TypeMeta
|
||||
gotFlags = got.Flags
|
||||
default:
|
||||
t.Fatalf("unexpected API version: %q", apiVersion)
|
||||
}
|
||||
return gotTypeMeta, gotName, gotFlags
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ import (
|
|||
"time"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
|
||||
|
|
@ -37,10 +36,15 @@ import (
|
|||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
apiserverfeat "k8s.io/apiserver/pkg/features"
|
||||
flagzv1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
flagzv1beta1 "k8s.io/apiserver/pkg/server/flagz/api/v1beta1"
|
||||
flagztesting "k8s.io/apiserver/pkg/server/flagz/testing"
|
||||
statuszv1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1"
|
||||
statuszv1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1"
|
||||
statusztesting "k8s.io/apiserver/pkg/server/statusz/testing"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
|
|
@ -50,11 +54,6 @@ import (
|
|||
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||
"k8s.io/kubernetes/test/integration/etcd"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
|
||||
flagzv1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
statuszv1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1"
|
||||
statuszv1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1"
|
||||
statusztesting "k8s.io/apiserver/pkg/server/statusz/testing"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -151,7 +150,7 @@ func TestFlagz(t *testing.T) {
|
|||
}
|
||||
|
||||
wantBodyStr := "apiserver flagz\nWarning: This endpoint is not meant to be machine parseable"
|
||||
wantBodyJSON := &flagzv1alpha1.Flagz{
|
||||
wantBodyAlpha := &flagzv1alpha1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1alpha1",
|
||||
|
|
@ -159,32 +158,48 @@ func TestFlagz(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apiserver",
|
||||
},
|
||||
Flags: map[string]string{
|
||||
"v": "2",
|
||||
},
|
||||
}
|
||||
wantBodyBeta := &flagzv1beta1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apiserver",
|
||||
},
|
||||
Flags: map[string]string{
|
||||
"v": "2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
acceptHeader string
|
||||
wantStatus int
|
||||
wantBodySub string // for text/plain
|
||||
wantStructuredBody *flagzv1alpha1.Flagz // for structured responses (JSON/YAML/CBOR)
|
||||
name string
|
||||
acceptHeader string
|
||||
wantStatus int
|
||||
wantBodyText string // for text/plain
|
||||
wantBodyStructured interface{} // for structured responses (JSON/YAML/CBOR)
|
||||
wantDeprecationHeader bool
|
||||
}{
|
||||
{
|
||||
name: "text plain response",
|
||||
acceptHeader: "text/plain",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: wantBodyStr,
|
||||
wantBodyText: wantBodyStr,
|
||||
},
|
||||
{
|
||||
name: "structured json response",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantStructuredBody: wantBodyJSON,
|
||||
wantBodyStructured: wantBodyBeta,
|
||||
},
|
||||
{
|
||||
name: "no accept header (defaults to text)",
|
||||
acceptHeader: "",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: wantBodyStr,
|
||||
wantBodyText: wantBodyStr,
|
||||
},
|
||||
{
|
||||
name: "invalid accept header",
|
||||
|
|
@ -198,32 +213,46 @@ func TestFlagz(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "application/json with missing as",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io",
|
||||
wantStatus: http.StatusNotAcceptable,
|
||||
},
|
||||
{
|
||||
name: "wildcard accept header",
|
||||
acceptHeader: "*/*",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: wantBodyStr,
|
||||
wantBodyText: wantBodyStr,
|
||||
},
|
||||
{
|
||||
name: "bad json header fall back wildcard",
|
||||
acceptHeader: "application/json;v=foo;g=config.k8s.io;as=Flagz,*/*",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: wantBodyStr,
|
||||
wantBodyText: wantBodyStr,
|
||||
},
|
||||
{
|
||||
name: "structured cbor response",
|
||||
acceptHeader: "application/cbor;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
acceptHeader: "application/cbor;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantStructuredBody: wantBodyJSON,
|
||||
wantBodyStructured: wantBodyBeta,
|
||||
},
|
||||
{
|
||||
name: "structured yaml response",
|
||||
acceptHeader: "application/yaml;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
acceptHeader: "application/yaml;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantStructuredBody: wantBodyJSON,
|
||||
wantBodyStructured: wantBodyBeta,
|
||||
},
|
||||
{
|
||||
name: "alpha specified before beta, should show warning",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz,application/json;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: wantBodyAlpha,
|
||||
wantDeprecationHeader: true,
|
||||
},
|
||||
{
|
||||
name: "beta specified before alpha, no warning",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Flagz,application/json;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: wantBodyBeta,
|
||||
wantDeprecationHeader: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
@ -240,24 +269,17 @@ func TestFlagz(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if tc.wantStatus == http.StatusOK {
|
||||
if tc.wantBodySub != "" {
|
||||
if !bytes.Contains(raw, []byte(tc.wantBodySub)) {
|
||||
t.Errorf("body missing expected substring: %q\nGot:\n%s", tc.wantBodySub, string(raw))
|
||||
if tc.wantBodyText != "" {
|
||||
if !bytes.Contains(raw, []byte(tc.wantBodyText)) {
|
||||
t.Errorf("body missing expected substring: %q\nGot:\n%s", tc.wantBodyText, string(raw))
|
||||
}
|
||||
}
|
||||
if tc.wantStructuredBody != nil {
|
||||
var got flagzv1alpha1.Flagz
|
||||
unmarshalResponse(t, tc.acceptHeader, raw, &got)
|
||||
// Only check static fields, since others are dynamic
|
||||
if got.TypeMeta != tc.wantStructuredBody.TypeMeta {
|
||||
t.Errorf("TypeMeta mismatch: want %+v, got %+v", tc.wantStructuredBody.TypeMeta, got.TypeMeta)
|
||||
}
|
||||
if got.ObjectMeta.Name != tc.wantStructuredBody.ObjectMeta.Name {
|
||||
t.Errorf("ObjectMeta.Name mismatch: want %q, got %q", tc.wantStructuredBody.ObjectMeta.Name, got.ObjectMeta.Name)
|
||||
}
|
||||
if got.Flags["v"] != "2" {
|
||||
t.Errorf("v mismatch: want %q, got %q", "2", got.Flags["v"])
|
||||
}
|
||||
var warnings []string
|
||||
for _, w := range res.Warnings() {
|
||||
warnings = append(warnings, w.Text)
|
||||
}
|
||||
if tc.wantBodyStructured != nil {
|
||||
flagztesting.VerifyStructuredResponse(t, tc.acceptHeader, raw, warnings, tc.wantBodyStructured, tc.wantDeprecationHeader)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -878,23 +900,3 @@ func TestMultiAPIServerNodePortAllocation(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func unmarshalResponse(t *testing.T, acceptHeader string, raw []byte, got interface{}) {
|
||||
t.Helper()
|
||||
switch {
|
||||
case strings.Contains(acceptHeader, "application/json"):
|
||||
if err := json.Unmarshal(raw, got); err != nil {
|
||||
t.Fatalf("error unmarshalling JSON: %v", err)
|
||||
}
|
||||
case strings.Contains(acceptHeader, "application/yaml"):
|
||||
if err := yaml.Unmarshal(raw, got); err != nil {
|
||||
t.Fatalf("error unmarshalling YAML: %v", err)
|
||||
}
|
||||
case strings.Contains(acceptHeader, "application/cbor"):
|
||||
if err := cbor.Unmarshal(raw, got); err != nil {
|
||||
t.Fatalf("error unmarshalling CBOR: %v", err)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unexpected accept header for structured body: %s", acceptHeader)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package serving
|
|||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
|
@ -32,6 +31,8 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
apiserverfeat "k8s.io/apiserver/pkg/features"
|
||||
flagzv1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
flagzv1beta1 "k8s.io/apiserver/pkg/server/flagz/api/v1beta1"
|
||||
flagztesting "k8s.io/apiserver/pkg/server/flagz/testing"
|
||||
statuszv1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1"
|
||||
statuszv1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1"
|
||||
statusztesting "k8s.io/apiserver/pkg/server/statusz/testing"
|
||||
|
|
@ -270,7 +271,7 @@ func TestSchedulerZPages(t *testing.T) {
|
|||
}
|
||||
|
||||
flagzWantBodyStr := "kube-scheduler flagz\nWarning: This endpoint is not meant to be machine parseable"
|
||||
flagzWantBodyJSON := &flagzv1alpha1.Flagz{
|
||||
flagzWantBodyStructuredAlpha := &flagzv1alpha1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1alpha1",
|
||||
|
|
@ -278,6 +279,21 @@ func TestSchedulerZPages(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kube-scheduler",
|
||||
},
|
||||
Flags: map[string]string{
|
||||
"leader-elect-resource-name": "kube-scheduler",
|
||||
},
|
||||
}
|
||||
flagzWantBodyStructuredBeta := &flagzv1beta1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kube-scheduler",
|
||||
},
|
||||
Flags: map[string]string{
|
||||
"leader-elect-resource-name": "kube-scheduler",
|
||||
},
|
||||
}
|
||||
|
||||
statuszTestCases := []struct {
|
||||
|
|
@ -362,29 +378,30 @@ func TestSchedulerZPages(t *testing.T) {
|
|||
}
|
||||
|
||||
flagzTestCases := []struct {
|
||||
name string
|
||||
acceptHeader string
|
||||
wantStatus int
|
||||
wantBodySub string // for text/plain
|
||||
wantJSON *flagzv1alpha1.Flagz // for structured json
|
||||
name string
|
||||
acceptHeader string
|
||||
wantStatus int
|
||||
wantBodyText string
|
||||
wantBodyStructured interface{}
|
||||
wantDeprecationHeader bool
|
||||
}{
|
||||
{
|
||||
name: "text plain response",
|
||||
acceptHeader: "text/plain",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: flagzWantBodyStr,
|
||||
wantBodyText: flagzWantBodyStr,
|
||||
},
|
||||
{
|
||||
name: "structured json response",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantJSON: flagzWantBodyJSON,
|
||||
name: "structured json response",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredBeta,
|
||||
},
|
||||
{
|
||||
name: "no accept header (defaults to text)",
|
||||
acceptHeader: "",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: flagzWantBodyStr,
|
||||
wantBodyText: flagzWantBodyStr,
|
||||
},
|
||||
{
|
||||
name: "invalid accept header",
|
||||
|
|
@ -398,20 +415,46 @@ func TestSchedulerZPages(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "application/json with missing as",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io",
|
||||
wantStatus: http.StatusNotAcceptable,
|
||||
},
|
||||
{
|
||||
name: "wildcard accept header",
|
||||
acceptHeader: "*/*",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: flagzWantBodyStr,
|
||||
wantBodyText: flagzWantBodyStr,
|
||||
},
|
||||
{
|
||||
name: "bad json header fall back wildcard",
|
||||
acceptHeader: "application/json;v=foo;g=config.k8s.io;as=Flagz,*/*",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: flagzWantBodyStr,
|
||||
wantBodyText: flagzWantBodyStr,
|
||||
},
|
||||
{
|
||||
name: "structured cbor response",
|
||||
acceptHeader: "application/cbor;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredBeta,
|
||||
},
|
||||
{
|
||||
name: "structured yaml response",
|
||||
acceptHeader: "application/yaml;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredBeta,
|
||||
},
|
||||
{
|
||||
name: "alpha specified before beta, should show warning",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz,application/json;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredAlpha,
|
||||
wantDeprecationHeader: true,
|
||||
},
|
||||
{
|
||||
name: "beta specified before alpha, no warning",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Flagz,application/json;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredBeta,
|
||||
wantDeprecationHeader: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -482,23 +525,14 @@ func TestSchedulerZPages(t *testing.T) {
|
|||
}
|
||||
|
||||
if tc.wantStatus == http.StatusOK {
|
||||
if tc.wantBodySub != "" {
|
||||
if !strings.Contains(string(body), tc.wantBodySub) {
|
||||
t.Errorf("body missing expected substring: %q\nGot:\n%s", tc.wantBodySub, string(body))
|
||||
if tc.wantBodyText != "" {
|
||||
if !strings.Contains(string(body), tc.wantBodyText) {
|
||||
t.Errorf("body missing expected substring: %q\nGot:\n%s", tc.wantBodyText, string(body))
|
||||
}
|
||||
}
|
||||
if tc.wantJSON != nil {
|
||||
var got flagzv1alpha1.Flagz
|
||||
if err := json.Unmarshal(body, &got); err != nil {
|
||||
t.Fatalf("error unmarshalling JSON: %v", err)
|
||||
}
|
||||
// Only check static fields, since others are dynamic
|
||||
if got.TypeMeta != tc.wantJSON.TypeMeta {
|
||||
t.Errorf("TypeMeta mismatch: want %+v, got %+v", tc.wantJSON.TypeMeta, got.TypeMeta)
|
||||
}
|
||||
if got.ObjectMeta.Name != tc.wantJSON.ObjectMeta.Name {
|
||||
t.Errorf("ObjectMeta.Name mismatch: want %q, got %q", tc.wantJSON.ObjectMeta.Name, got.ObjectMeta.Name)
|
||||
}
|
||||
if tc.wantBodyStructured != nil {
|
||||
warnings := append([]string{}, r.Header.Values("Warning")...)
|
||||
flagztesting.VerifyStructuredResponse(t, tc.acceptHeader, body, warnings, tc.wantBodyStructured, tc.wantDeprecationHeader)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
|
@ -32,16 +31,17 @@ import (
|
|||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
flagzv1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
statusztesting "k8s.io/apiserver/pkg/server/statusz/testing"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
apiserverfeat "k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/server"
|
||||
flagzv1alpha1 "k8s.io/apiserver/pkg/server/flagz/api/v1alpha1"
|
||||
flagzv1beta1 "k8s.io/apiserver/pkg/server/flagz/api/v1beta1"
|
||||
flagztesting "k8s.io/apiserver/pkg/server/flagz/testing"
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
statuszv1alpha1 "k8s.io/apiserver/pkg/server/statusz/api/v1alpha1"
|
||||
statuszv1beta1 "k8s.io/apiserver/pkg/server/statusz/api/v1beta1"
|
||||
statusztesting "k8s.io/apiserver/pkg/server/statusz/testing"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
cloudctrlmgrtesting "k8s.io/cloud-provider/app/testing"
|
||||
"k8s.io/cloud-provider/fake"
|
||||
|
|
@ -381,8 +381,8 @@ users:
|
|||
Paths: []string{"/configz", "/flagz", "/healthz", "/metrics"},
|
||||
}
|
||||
|
||||
flagzWantBodyStr := "kube-controller-manager flagz\nWarning: This endpoint is not meant to be machine parseable"
|
||||
flagzWantBodyJSON := &flagzv1alpha1.Flagz{
|
||||
flagzWantBodyText := "kube-controller-manager flagz\nWarning: This endpoint is not meant to be machine parseable"
|
||||
flagzWantBodyStructuredAlpha := &flagzv1alpha1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1alpha1",
|
||||
|
|
@ -391,6 +391,15 @@ users:
|
|||
Name: "kube-controller-manager",
|
||||
},
|
||||
}
|
||||
flagzWantBodyStructuredBeta := &flagzv1beta1.Flagz{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Flagz",
|
||||
APIVersion: "config.k8s.io/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kube-controller-manager",
|
||||
},
|
||||
}
|
||||
|
||||
statuszTestCases := []struct {
|
||||
name string
|
||||
|
|
@ -474,29 +483,30 @@ users:
|
|||
}
|
||||
|
||||
flagzTestCases := []struct {
|
||||
name string
|
||||
acceptHeader string
|
||||
wantStatus int
|
||||
wantBodySub string // for text/plain
|
||||
wantJSON *flagzv1alpha1.Flagz // for structured json
|
||||
name string
|
||||
acceptHeader string
|
||||
wantStatus int
|
||||
wantBodyText string // for text/plain
|
||||
wantBodyStructured interface{} // for structured json
|
||||
wantDeprecationHeader bool
|
||||
}{
|
||||
{
|
||||
name: "text plain response",
|
||||
acceptHeader: "text/plain",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: flagzWantBodyStr,
|
||||
wantBodyText: flagzWantBodyText,
|
||||
},
|
||||
{
|
||||
name: "structured json response",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantJSON: flagzWantBodyJSON,
|
||||
name: "structured json response",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredBeta,
|
||||
},
|
||||
{
|
||||
name: "no accept header (defaults to text)",
|
||||
acceptHeader: "",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: flagzWantBodyStr,
|
||||
wantBodyText: flagzWantBodyText,
|
||||
},
|
||||
{
|
||||
name: "invalid accept header",
|
||||
|
|
@ -510,20 +520,46 @@ users:
|
|||
},
|
||||
{
|
||||
name: "application/json with missing as",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io",
|
||||
wantStatus: http.StatusNotAcceptable,
|
||||
},
|
||||
{
|
||||
name: "wildcard accept header",
|
||||
acceptHeader: "*/*",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: flagzWantBodyStr,
|
||||
wantBodyText: flagzWantBodyText,
|
||||
},
|
||||
{
|
||||
name: "bad json header fall back wildcard",
|
||||
acceptHeader: "application/json;v=foo;g=config.k8s.io;as=Flagz,*/*",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodySub: flagzWantBodyStr,
|
||||
wantBodyText: flagzWantBodyText,
|
||||
},
|
||||
{
|
||||
name: "structured cbor response",
|
||||
acceptHeader: "application/cbor;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredBeta,
|
||||
},
|
||||
{
|
||||
name: "structured yaml response",
|
||||
acceptHeader: "application/yaml;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredBeta,
|
||||
},
|
||||
{
|
||||
name: "alpha specified before beta, should show warning",
|
||||
acceptHeader: "application/json;v=v1alpha1;g=config.k8s.io;as=Flagz,application/json;v=v1beta1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredAlpha,
|
||||
wantDeprecationHeader: true,
|
||||
},
|
||||
{
|
||||
name: "beta specified before alpha, no warning",
|
||||
acceptHeader: "application/json;v=v1beta1;g=config.k8s.io;as=Flagz,application/json;v=v1alpha1;g=config.k8s.io;as=Flagz",
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyStructured: flagzWantBodyStructuredBeta,
|
||||
wantDeprecationHeader: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -640,23 +676,14 @@ users:
|
|||
}
|
||||
|
||||
if tc.wantStatus == http.StatusOK {
|
||||
if tc.wantBodySub != "" {
|
||||
if !strings.Contains(string(body), tc.wantBodySub) {
|
||||
t.Errorf("body missing expected substring: %q\nGot:\n%s", tc.wantBodySub, string(body))
|
||||
if tc.wantBodyText != "" {
|
||||
if !strings.Contains(string(body), tc.wantBodyText) {
|
||||
t.Errorf("body missing expected substring: %q\nGot:\n%s", tc.wantBodyText, string(body))
|
||||
}
|
||||
}
|
||||
if tc.wantJSON != nil {
|
||||
var got flagzv1alpha1.Flagz
|
||||
if err := json.Unmarshal(body, &got); err != nil {
|
||||
t.Fatalf("error unmarshalling JSON: %v", err)
|
||||
}
|
||||
// Only check static fields, since others are dynamic
|
||||
if got.TypeMeta != tc.wantJSON.TypeMeta {
|
||||
t.Errorf("TypeMeta mismatch: want %+v, got %+v", tc.wantJSON.TypeMeta, got.TypeMeta)
|
||||
}
|
||||
if got.ObjectMeta.Name != tc.wantJSON.ObjectMeta.Name {
|
||||
t.Errorf("ObjectMeta.Name mismatch: want %q, got %q", tc.wantJSON.ObjectMeta.Name, got.ObjectMeta.Name)
|
||||
}
|
||||
if tc.wantBodyStructured != nil {
|
||||
warnings := append([]string{}, r.Header.Values("Warning")...)
|
||||
flagztesting.VerifyStructuredResponse(t, tc.acceptHeader, body, warnings, tc.wantBodyStructured, tc.wantDeprecationHeader)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue