Skip to content

Commit

Permalink
Merge pull request #100715 from kevindelgado/automated-cherry-pick-of…
Browse files Browse the repository at this point in the history
…-#100341-#98576-#--dry-run-upstream-release-1.18-1617135987

cherry pick  #100341 #98576 #95836 on 1.18 to enable SSA with APIService
  • Loading branch information
k8s-ci-robot committed Apr 9, 2021
2 parents c7c1ad4 + e71a2eb commit c552288
Show file tree
Hide file tree
Showing 12 changed files with 121 additions and 40 deletions.
3 changes: 3 additions & 0 deletions cmd/kube-apiserver/app/aggregator.go
Expand Up @@ -67,6 +67,9 @@ func createAggregatorConfig(
genericConfig := kubeAPIServerConfig
genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{}
genericConfig.RESTOptionsGetter = nil
// prevent generic API server from installing the OpenAPI handler. Aggregator server
// has its own customized OpenAPI handler.
genericConfig.SkipOpenAPIInstallation = true

// override genericConfig.AdmissionControl with kube-aggregator's scheme,
// because aggregator apiserver should use its own scheme to convert its own resources.
Expand Down
33 changes: 18 additions & 15 deletions staging/src/k8s.io/apimachinery/pkg/util/json/json.go
Expand Up @@ -39,7 +39,8 @@ func Marshal(v interface{}) ([]byte, error) {
const maxDepth = 10000

// Unmarshal unmarshals the given data
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
// If v is a *map[string]interface{}, *[]interface{}, or *interface{} numbers
// are converted to int64 or float64
func Unmarshal(data []byte, v interface{}) error {
switch v := v.(type) {
case *map[string]interface{}:
Expand All @@ -52,7 +53,7 @@ func Unmarshal(data []byte, v interface{}) error {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertMapNumbers(*v, 0)
return ConvertMapNumbers(*v, 0)

case *[]interface{}:
// Build a decoder from the given data
Expand All @@ -64,7 +65,7 @@ func Unmarshal(data []byte, v interface{}) error {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertSliceNumbers(*v, 0)
return ConvertSliceNumbers(*v, 0)

case *interface{}:
// Build a decoder from the given data
Expand All @@ -76,29 +77,31 @@ func Unmarshal(data []byte, v interface{}) error {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertInterfaceNumbers(v, 0)
return ConvertInterfaceNumbers(v, 0)

default:
return json.Unmarshal(data, v)
}
}

func convertInterfaceNumbers(v *interface{}, depth int) error {
// ConvertInterfaceNumbers converts any json.Number values to int64 or float64.
// Values which are map[string]interface{} or []interface{} are recursively visited
func ConvertInterfaceNumbers(v *interface{}, depth int) error {
var err error
switch v2 := (*v).(type) {
case json.Number:
*v, err = convertNumber(v2)
case map[string]interface{}:
err = convertMapNumbers(v2, depth+1)
err = ConvertMapNumbers(v2, depth+1)
case []interface{}:
err = convertSliceNumbers(v2, depth+1)
err = ConvertSliceNumbers(v2, depth+1)
}
return err
}

// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.
// ConvertMapNumbers traverses the map, converting any json.Number values to int64 or float64.
// values which are map[string]interface{} or []interface{} are recursively visited
func convertMapNumbers(m map[string]interface{}, depth int) error {
func ConvertMapNumbers(m map[string]interface{}, depth int) error {
if depth > maxDepth {
return fmt.Errorf("exceeded max depth of %d", maxDepth)
}
Expand All @@ -109,9 +112,9 @@ func convertMapNumbers(m map[string]interface{}, depth int) error {
case json.Number:
m[k], err = convertNumber(v)
case map[string]interface{}:
err = convertMapNumbers(v, depth+1)
err = ConvertMapNumbers(v, depth+1)
case []interface{}:
err = convertSliceNumbers(v, depth+1)
err = ConvertSliceNumbers(v, depth+1)
}
if err != nil {
return err
Expand All @@ -120,9 +123,9 @@ func convertMapNumbers(m map[string]interface{}, depth int) error {
return nil
}

// convertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64.
// ConvertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64.
// values which are map[string]interface{} or []interface{} are recursively visited
func convertSliceNumbers(s []interface{}, depth int) error {
func ConvertSliceNumbers(s []interface{}, depth int) error {
if depth > maxDepth {
return fmt.Errorf("exceeded max depth of %d", maxDepth)
}
Expand All @@ -133,9 +136,9 @@ func convertSliceNumbers(s []interface{}, depth int) error {
case json.Number:
s[i], err = convertNumber(v)
case map[string]interface{}:
err = convertMapNumbers(v, depth+1)
err = ConvertMapNumbers(v, depth+1)
case []interface{}:
err = convertSliceNumbers(v, depth+1)
err = ConvertSliceNumbers(v, depth+1)
}
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions staging/src/k8s.io/apimachinery/pkg/util/yaml/BUILD
Expand Up @@ -18,6 +18,7 @@ go_library(
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/yaml",
importpath = "k8s.io/apimachinery/pkg/util/yaml",
deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
Expand Down
31 changes: 31 additions & 0 deletions staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder.go
Expand Up @@ -26,10 +26,41 @@ import (
"strings"
"unicode"

jsonutil "k8s.io/apimachinery/pkg/util/json"
"k8s.io/klog"

"sigs.k8s.io/yaml"
)

// Unmarshal unmarshals the given data
// If v is a *map[string]interface{}, *[]interface{}, or *interface{} numbers
// are converted to int64 or float64
func Unmarshal(data []byte, v interface{}) error {
preserveIntFloat := func(d *json.Decoder) *json.Decoder {
d.UseNumber()
return d
}
switch v := v.(type) {
case *map[string]interface{}:
if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil {
return err
}
return jsonutil.ConvertMapNumbers(*v, 0)
case *[]interface{}:
if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil {
return err
}
return jsonutil.ConvertSliceNumbers(*v, 0)
case *interface{}:
if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil {
return err
}
return jsonutil.ConvertInterfaceNumbers(v, 0)
default:
return yaml.Unmarshal(data, v)
}
}

// ToJSON converts a single YAML document into a JSON document
// or returns an error. If the document appears to be JSON the
// YAML decoding path is not used (so that error messages are
Expand Down
41 changes: 41 additions & 0 deletions staging/src/k8s.io/apimachinery/pkg/util/yaml/decoder_test.go
Expand Up @@ -403,3 +403,44 @@ stuff: 1
t.Fatalf("expected %q to be of type YAMLSyntaxError", err.Error())
}
}

func TestUnmarshal(t *testing.T) {
mapWithIntegerBytes := []byte(`replicas: 1`)
mapWithInteger := make(map[string]interface{})
if err := Unmarshal(mapWithIntegerBytes, &mapWithInteger); err != nil {
t.Fatalf("unexpected error unmarshaling yaml: %v", err)
}
if _, ok := mapWithInteger["replicas"].(int64); !ok {
t.Fatalf(`Expected number in map to be int64 but got "%T"`, mapWithInteger["replicas"])
}

sliceWithIntegerBytes := []byte(`- 1`)
var sliceWithInteger []interface{}
if err := Unmarshal(sliceWithIntegerBytes, &sliceWithInteger); err != nil {
t.Fatalf("unexpected error unmarshaling yaml: %v", err)
}
if _, ok := sliceWithInteger[0].(int64); !ok {
t.Fatalf(`Expected number in slice to be int64 but got "%T"`, sliceWithInteger[0])
}

integerBytes := []byte(`1`)
var integer interface{}
if err := Unmarshal(integerBytes, &integer); err != nil {
t.Fatalf("unexpected error unmarshaling yaml: %v", err)
}
if _, ok := integer.(int64); !ok {
t.Fatalf(`Expected number to be int64 but got "%T"`, integer)
}

otherTypeBytes := []byte(`123: 2`)
otherType := make(map[int]interface{})
if err := Unmarshal(otherTypeBytes, &otherType); err != nil {
t.Fatalf("unexpected error unmarshaling yaml: %v", err)
}
if _, ok := otherType[123].(int64); ok {
t.Fatalf(`Expected number not to be converted to int64`)
}
if _, ok := otherType[123].(float64); !ok {
t.Fatalf(`Expected number to be float64 but got "%T"`, otherType[123])
}
}
2 changes: 1 addition & 1 deletion staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD
Expand Up @@ -86,6 +86,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/audit:go_default_library",
Expand All @@ -107,7 +108,6 @@ go_library(
"//vendor/google.golang.org/grpc/status:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/trace:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)

Expand Down
Expand Up @@ -38,6 +38,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authorization/authorizer"
Expand All @@ -49,7 +50,6 @@ import (
"k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utiltrace "k8s.io/utils/trace"
"sigs.k8s.io/yaml"
)

const (
Expand Down
5 changes: 4 additions & 1 deletion staging/src/k8s.io/apiserver/pkg/server/config.go
Expand Up @@ -160,6 +160,8 @@ type Config struct {
Serializer runtime.NegotiatedSerializer
// OpenAPIConfig will be used in generating OpenAPI spec. This is nil by default. Use DefaultOpenAPIConfig for "working" defaults.
OpenAPIConfig *openapicommon.Config
// SkipOpenAPIInstallation avoids installing the OpenAPI handler if set to true.
SkipOpenAPIInstallation bool

// RESTOptionsGetter is used to construct RESTStorage types via the generic registry.
RESTOptionsGetter genericregistry.RESTOptionsGetter
Expand Down Expand Up @@ -558,7 +560,8 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G

listedPathProvider: apiServerHandler,

openAPIConfig: c.OpenAPIConfig,
openAPIConfig: c.OpenAPIConfig,
skipOpenAPIInstallation: c.SkipOpenAPIInstallation,

postStartHooks: map[string]postStartHookEntry{},
preShutdownHooks: map[string]preShutdownHookEntry{},
Expand Down
10 changes: 8 additions & 2 deletions staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go
Expand Up @@ -129,8 +129,14 @@ type GenericAPIServer struct {
// Enable swagger and/or OpenAPI if these configs are non-nil.
openAPIConfig *openapicommon.Config

// SkipOpenAPIInstallation indicates not to install the OpenAPI handler
// during PrepareRun.
// Set this to true when the specific API Server has its own OpenAPI handler
// (e.g. kube-aggregator)
skipOpenAPIInstallation bool

// OpenAPIVersionedService controls the /openapi/v2 endpoint, and can be used to update the served spec.
// It is set during PrepareRun.
// It is set during PrepareRun if `openAPIConfig` is non-nil unless `skipOpenAPIInstallation` is true.
OpenAPIVersionedService *handler.OpenAPIService

// StaticOpenAPISpec is the spec derived from the restful container endpoints.
Expand Down Expand Up @@ -283,7 +289,7 @@ type preparedGenericAPIServer struct {
func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
s.delegationTarget.PrepareRun()

if s.openAPIConfig != nil {
if s.openAPIConfig != nil && !s.skipOpenAPIInstallation {
s.OpenAPIVersionedService, s.StaticOpenAPISpec = routes.OpenAPI{
Config: s.openAPIConfig,
}.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux)
Expand Down
Expand Up @@ -158,11 +158,6 @@ func (cfg *Config) Complete() CompletedConfig {

// NewWithDelegate returns a new instance of APIAggregator from the given config.
func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.DelegationTarget) (*APIAggregator, error) {
// Prevent generic API server to install OpenAPI handler. Aggregator server
// has its own customized OpenAPI handler.
openAPIConfig := c.GenericConfig.OpenAPIConfig
c.GenericConfig.OpenAPIConfig = nil

genericServer, err := c.GenericConfig.New("kube-aggregator", delegationTarget)
if err != nil {
return nil, err
Expand All @@ -188,7 +183,7 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
lister: informerFactory.Apiregistration().V1().APIServices().Lister(),
APIRegistrationInformers: informerFactory,
serviceResolver: c.ExtraConfig.ServiceResolver,
openAPIConfig: openAPIConfig,
openAPIConfig: c.GenericConfig.OpenAPIConfig,
egressSelector: c.GenericConfig.EgressSelector,
}

Expand Down
24 changes: 11 additions & 13 deletions test/integration/apiserver/apply/status_test.go
Expand Up @@ -57,15 +57,7 @@ var statusData = map[schema.GroupVersionResource]string{
gvr("certificates.k8s.io", "v1beta1", "certificatesigningrequests"): `{"status": {"conditions": [{"type": "MyStatus"}]}}`,
}

const statusDefault = `{"status": {"conditions": [{"type": "MyStatus", "status":"true"}]}}`

// DO NOT ADD TO THIS LIST.
// This list is used to ignore known bugs. We shouldn't introduce new bugs.
var ignoreList = map[schema.GroupVersionResource]struct{}{
// TODO(#89264): apiservices doesn't work because the openapi is not routed properly.
gvr("apiregistration.k8s.io", "v1beta1", "apiservices"): {},
gvr("apiregistration.k8s.io", "v1", "apiservices"): {},
}
const statusDefault = `{"status": {"conditions": [{"type": "MyStatus", "status":"True"}]}}`

func gvr(g, v, r string) schema.GroupVersionResource {
return schema.GroupVersionResource{Group: g, Version: v, Resource: r}
Expand Down Expand Up @@ -137,9 +129,6 @@ func TestApplyStatus(t *testing.T) {
t.Fatal(err)
}
t.Run(mapping.Resource.String(), func(t *testing.T) {
if _, ok := ignoreList[mapping.Resource]; ok {
t.Skip()
}
status, ok := statusData[mapping.Resource]
if !ok {
status = statusDefault
Expand All @@ -158,8 +147,17 @@ func TestApplyStatus(t *testing.T) {
namespace = ""
}
name := newObj.GetName()

// etcd test stub data doesn't contain apiVersion/kind (!), but apply requires it
newObj.SetGroupVersionKind(mapping.GroupVersionKind)
createData, err := json.Marshal(newObj.Object)
if err != nil {
t.Fatal(err)
}

rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace)
_, err := rsc.Create(context.TODO(), &newObj, metav1.CreateOptions{FieldManager: "create_test"})
// apply to create
_, err = rsc.Patch(context.TODO(), name, types.ApplyPatchType, []byte(createData), metav1.PatchOptions{FieldManager: "create_test"})
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion test/integration/etcd/data.go
Expand Up @@ -44,7 +44,7 @@ func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionRes
ExpectedEtcdPath: "/registry/configmaps/" + namespace + "/cm1",
},
gvr("", "v1", "services"): {
Stub: `{"metadata": {"name": "service1"}, "spec": {"externalName": "service1name", "ports": [{"port": 10000, "targetPort": 11000}], "selector": {"test": "data"}}}`,
Stub: `{"metadata": {"name": "service1"}, "spec": {"externalName": "service1name", "ports": [{"port": 10000, "targetPort": 11000, "protocol":"TCP"}], "selector": {"test": "data"}}}`,
ExpectedEtcdPath: "/registry/services/specs/" + namespace + "/service1",
},
gvr("", "v1", "podtemplates"): {
Expand Down

0 comments on commit c552288

Please sign in to comment.