Merge pull request #83204 from obitech/kubelet_strict_serializer

Enable strict serializer in kubelet
This commit is contained in:
Kubernetes Prow Robot 2019-10-31 09:11:37 -07:00 committed by GitHub
commit 3383d7ca66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 65 deletions

View File

@ -26,8 +26,9 @@ import (
// Utility functions for the Kubelet's kubeletconfig API group
// NewSchemeAndCodecs is a utility function that returns a Scheme and CodecFactory
// that understand the types in the kubeletconfig API group.
func NewSchemeAndCodecs() (*runtime.Scheme, *serializer.CodecFactory, error) {
// that understand the types in the kubeletconfig API group. Passing mutators allows
// for adjusting the behavior of the CodecFactory, for example enable strict decoding.
func NewSchemeAndCodecs(mutators ...serializer.CodecFactoryOptionsMutator) (*runtime.Scheme, *serializer.CodecFactory, error) {
scheme := runtime.NewScheme()
if err := kubeletconfig.AddToScheme(scheme); err != nil {
return nil, nil, err
@ -35,6 +36,6 @@ func NewSchemeAndCodecs() (*runtime.Scheme, *serializer.CodecFactory, error) {
if err := kubeletconfigv1beta1.AddToScheme(scheme); err != nil {
return nil, nil, err
}
codecs := serializer.NewCodecFactory(scheme)
codecs := serializer.NewCodecFactory(scheme, mutators...)
return scheme, &codecs, nil
}

View File

@ -43,6 +43,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/kubelet/config/v1beta1:go_default_library",

View File

@ -26,6 +26,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
@ -106,8 +107,9 @@ func NewRemoteConfigSource(source *apiv1.NodeConfigSource) (RemoteConfigSource,
// DecodeRemoteConfigSource is a helper for using the apimachinery to decode serialized RemoteConfigSources;
// e.g. the metadata stored by checkpoint/store/fsstore.go
func DecodeRemoteConfigSource(data []byte) (RemoteConfigSource, error) {
// decode the remote config source
_, codecs, err := scheme.NewSchemeAndCodecs()
// Decode the remote config source. We want this to be non-strict
// so we don't error out on newer API keys.
_, codecs, err := scheme.NewSchemeAndCodecs(serializer.DisableStrict)
if err != nil {
return nil, err
}

View File

@ -43,6 +43,8 @@ go_test(
"//pkg/kubelet/kubeletconfig/util/test:go_default_library",
"//pkg/util/filesystem:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/kubelet/config/v1beta1:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)

View File

@ -45,7 +45,7 @@ type fsLoader struct {
// NewFsLoader returns a Loader that loads a KubeletConfiguration from the `kubeletFile`
func NewFsLoader(fs utilfs.Filesystem, kubeletFile string) (Loader, error) {
_, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs()
_, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs(serializer.EnableStrict)
if err != nil {
return nil, err
}

View File

@ -21,7 +21,10 @@ import (
"path/filepath"
"testing"
"github.com/pkg/errors"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/config/scheme"
@ -36,101 +39,114 @@ const kubeletFile = "kubelet"
func TestLoad(t *testing.T) {
cases := []struct {
desc string
file *string
expect *kubeletconfig.KubeletConfiguration
err string
desc string
file *string
expect *kubeletconfig.KubeletConfiguration
err string
strictErr bool
}{
// missing file
{
"missing file",
nil,
nil,
"failed to read",
desc: "missing file",
err: "failed to read",
},
// empty file
{
"empty file",
newString(``),
nil,
"was empty",
desc: "empty file",
file: newString(``),
err: "was empty",
},
// invalid format
{
"invalid yaml",
newString(`*`),
nil,
"failed to decode",
desc: "invalid yaml",
file: newString(`*`),
err: "failed to decode",
},
{
"invalid json",
newString(`{*`),
nil,
"failed to decode",
desc: "invalid json",
file: newString(`{*`),
err: "failed to decode",
},
// invalid object
{
"missing kind",
newString(`{"apiVersion":"kubelet.config.k8s.io/v1beta1"}`),
nil,
"failed to decode",
desc: "missing kind",
file: newString(`{"apiVersion":"kubelet.config.k8s.io/v1beta1"}`),
err: "failed to decode",
},
{
"missing version",
newString(`{"kind":"KubeletConfiguration"}`),
nil,
"failed to decode",
desc: "missing version",
file: newString(`{"kind":"KubeletConfiguration"}`),
err: "failed to decode",
},
{
"unregistered kind",
newString(`{"kind":"BogusKind","apiVersion":"kubelet.config.k8s.io/v1beta1"}`),
nil,
"failed to decode",
desc: "unregistered kind",
file: newString(`{"kind":"BogusKind","apiVersion":"kubelet.config.k8s.io/v1beta1"}`),
err: "failed to decode",
},
{
"unregistered version",
newString(`{"kind":"KubeletConfiguration","apiVersion":"bogusversion"}`),
nil,
"failed to decode",
desc: "unregistered version",
file: newString(`{"kind":"KubeletConfiguration","apiVersion":"bogusversion"}`),
err: "failed to decode",
},
// empty object with correct kind and version should result in the defaults for that kind and version
{
"default from yaml",
newString(`kind: KubeletConfiguration
desc: "default from yaml",
file: newString(`kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1`),
newConfig(t),
"",
expect: newConfig(t),
},
{
"default from json",
newString(`{"kind":"KubeletConfiguration","apiVersion":"kubelet.config.k8s.io/v1beta1"}`),
newConfig(t),
"",
desc: "default from json",
file: newString(`{"kind":"KubeletConfiguration","apiVersion":"kubelet.config.k8s.io/v1beta1"}`),
expect: newConfig(t),
},
// relative path
{
"yaml, relative path is resolved",
newString(fmt.Sprintf(`kind: KubeletConfiguration
desc: "yaml, relative path is resolved",
file: newString(fmt.Sprintf(`kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
staticPodPath: %s`, relativePath)),
func() *kubeletconfig.KubeletConfiguration {
expect: func() *kubeletconfig.KubeletConfiguration {
kc := newConfig(t)
kc.StaticPodPath = filepath.Join(configDir, relativePath)
return kc
}(),
"",
},
{
"json, relative path is resolved",
newString(fmt.Sprintf(`{"kind":"KubeletConfiguration","apiVersion":"kubelet.config.k8s.io/v1beta1","staticPodPath":"%s"}`, relativePath)),
func() *kubeletconfig.KubeletConfiguration {
desc: "json, relative path is resolved",
file: newString(fmt.Sprintf(`{"kind":"KubeletConfiguration","apiVersion":"kubelet.config.k8s.io/v1beta1","staticPodPath":"%s"}`, relativePath)),
expect: func() *kubeletconfig.KubeletConfiguration {
kc := newConfig(t)
kc.StaticPodPath = filepath.Join(configDir, relativePath)
return kc
}(),
"",
},
{
// This should fail from v1beta2+
desc: "duplicate field",
file: newString(fmt.Sprintf(`kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
staticPodPath: %s
staticPodPath: %s/foo`, relativePath, relativePath)),
// err: `key "staticPodPath" already set`,
// strictErr: true,
expect: func() *kubeletconfig.KubeletConfiguration {
kc := newConfig(t)
kc.StaticPodPath = filepath.Join(configDir, relativePath, "foo")
return kc
}(),
},
{
// This should fail from v1beta2+
desc: "unknown field",
file: newString(`kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
foo: bar`),
// err: "found unknown field: foo",
// strictErr: true,
expect: newConfig(t),
},
}
@ -148,6 +164,10 @@ staticPodPath: %s`, relativePath)),
t.Fatalf("unexpected error: %v", err)
}
kc, err := loader.Load()
if c.strictErr && !runtime.IsStrictDecodingError(errors.Cause(err)) {
t.Fatalf("got error: %v, want strict decoding error", err)
}
if utiltest.SkipRest(t, c.desc, err, c.err) {
return
}

View File

@ -14,9 +14,12 @@ go_library(
"//pkg/apis/core/install:go_default_library",
"//pkg/kubelet/apis/config:go_default_library",
"//pkg/kubelet/apis/config/scheme:go_default_library",
"//pkg/kubelet/apis/config/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)

View File

@ -19,6 +19,9 @@ package codec
import (
"fmt"
"github.com/pkg/errors"
"k8s.io/klog"
// ensure the core apis are installed
_ "k8s.io/kubernetes/pkg/apis/core/install"
@ -28,9 +31,10 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
"k8s.io/kubernetes/pkg/kubelet/apis/config/scheme"
kubeletconfigv1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/config/v1beta1"
)
// EncodeKubeletConfig encodes an internal KubeletConfiguration to an external YAML representation
// EncodeKubeletConfig encodes an internal KubeletConfiguration to an external YAML representation.
func EncodeKubeletConfig(internal *kubeletconfig.KubeletConfiguration, targetVersion schema.GroupVersion) ([]byte, error) {
encoder, err := NewKubeletconfigYAMLEncoder(targetVersion)
if err != nil {
@ -44,7 +48,7 @@ func EncodeKubeletConfig(internal *kubeletconfig.KubeletConfiguration, targetVer
return data, nil
}
// NewKubeletconfigYAMLEncoder returns an encoder that can write objects in the kubeletconfig API group to YAML
// NewKubeletconfigYAMLEncoder returns an encoder that can write objects in the kubeletconfig API group to YAML.
func NewKubeletconfigYAMLEncoder(targetVersion schema.GroupVersion) (runtime.Encoder, error) {
_, codecs, err := scheme.NewSchemeAndCodecs()
if err != nil {
@ -58,7 +62,7 @@ func NewKubeletconfigYAMLEncoder(targetVersion schema.GroupVersion) (runtime.Enc
return codecs.EncoderForVersion(info.Serializer, targetVersion), nil
}
// NewYAMLEncoder generates a new runtime.Encoder that encodes objects to YAML
// NewYAMLEncoder generates a new runtime.Encoder that encodes objects to YAML.
func NewYAMLEncoder(groupName string) (runtime.Encoder, error) {
// encode to YAML
mediaType := "application/yaml"
@ -72,16 +76,55 @@ func NewYAMLEncoder(groupName string) (runtime.Encoder, error) {
return nil, fmt.Errorf("no enabled versions for group %q", groupName)
}
// the "best" version supposedly comes first in the list returned from legacyscheme.Registry.EnabledVersionsForGroup
// the "best" version supposedly comes first in the list returned from legacyscheme.Registry.EnabledVersionsForGroup.
return legacyscheme.Codecs.EncoderForVersion(info.Serializer, versions[0]), nil
}
// DecodeKubeletConfiguration decodes a serialized KubeletConfiguration to the internal type
// newLenientSchemeAndCodecs returns a scheme that has only v1beta1 registered into
// it and a CodecFactory with strict decoding disabled.
func newLenientSchemeAndCodecs() (*runtime.Scheme, *serializer.CodecFactory, error) {
lenientScheme := runtime.NewScheme()
if err := kubeletconfig.AddToScheme(lenientScheme); err != nil {
return nil, nil, fmt.Errorf("failed to add internal kubelet config API to lenient scheme: %v", err)
}
if err := kubeletconfigv1beta1.AddToScheme(lenientScheme); err != nil {
return nil, nil, fmt.Errorf("failed to add kubelet config v1beta1 API to lenient scheme: %v", err)
}
lenientCodecs := serializer.NewCodecFactory(lenientScheme, serializer.DisableStrict)
return lenientScheme, &lenientCodecs, nil
}
// DecodeKubeletConfiguration decodes a serialized KubeletConfiguration to the internal type.
func DecodeKubeletConfiguration(kubeletCodecs *serializer.CodecFactory, data []byte) (*kubeletconfig.KubeletConfiguration, error) {
// the UniversalDecoder runs defaulting and returns the internal type by default
var (
obj runtime.Object
gvk *schema.GroupVersionKind
)
// The UniversalDecoder runs defaulting and returns the internal type by default.
obj, gvk, err := kubeletCodecs.UniversalDecoder().Decode(data, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to decode, error: %v", err)
// Try strict decoding first. If that fails decode with a lenient
// decoder, which has only v1beta1 registered, and log a warning.
// The lenient path is to be dropped when support for v1beta1 is dropped.
if !runtime.IsStrictDecodingError(err) {
return nil, errors.Wrap(err, "failed to decode")
}
var lenientErr error
_, lenientCodecs, lenientErr := newLenientSchemeAndCodecs()
if lenientErr != nil {
return nil, lenientErr
}
obj, gvk, lenientErr = lenientCodecs.UniversalDecoder().Decode(data, nil, nil)
if lenientErr != nil {
// Lenient decoding failed with the current version, return the
// original strict error.
return nil, fmt.Errorf("failed lenient decoding: %v", err)
}
// Continue with the v1beta1 object that was decoded leniently, but emit a warning.
klog.Warningf("using lenient decoding as strict decoding failed: %v", err)
}
internalKC, ok := obj.(*kubeletconfig.KubeletConfiguration)