diff --git a/cmd/kubelet/app/BUILD b/cmd/kubelet/app/BUILD index 71dd2850fe..e0ef19961d 100644 --- a/cmd/kubelet/app/BUILD +++ b/cmd/kubelet/app/BUILD @@ -41,6 +41,7 @@ go_library( "//pkg/features:go_default_library", "//pkg/kubelet:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/cadvisor:go_default_library", "//pkg/kubelet/certificate:go_default_library", diff --git a/cmd/kubelet/app/options/BUILD b/cmd/kubelet/app/options/BUILD index 2cb6e59bdb..9b19054d13 100644 --- a/cmd/kubelet/app/options/BUILD +++ b/cmd/kubelet/app/options/BUILD @@ -12,11 +12,10 @@ go_library( "options.go", ], deps = [ - "//pkg/api:go_default_library", "//pkg/apis/componentconfig:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", - "//pkg/kubelet/apis/kubeletconfig/install:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/apis/kubeletconfig/validation:go_default_library", "//pkg/util/taints:go_default_library", diff --git a/cmd/kubelet/app/options/options.go b/cmd/kubelet/app/options/options.go index 118d9c620a..ad60d5fa83 100644 --- a/cmd/kubelet/app/options/options.go +++ b/cmd/kubelet/app/options/options.go @@ -25,14 +25,12 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/util/flag" utilflag "k8s.io/apiserver/pkg/util/flag" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/componentconfig" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" - kubeletconfigvalidation "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation" - // Need to make sure the kubeletconfig api is installed so defaulting funcs work - _ "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/install" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + kubeletconfigvalidation "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation" utiltaints "k8s.io/kubernetes/pkg/util/taints" "github.com/spf13/pflag" @@ -142,10 +140,14 @@ func ValidateKubeletFlags(f *KubeletFlags) error { // NewKubeletConfiguration will create a new KubeletConfiguration with default values func NewKubeletConfiguration() (*kubeletconfig.KubeletConfiguration, error) { + scheme, _, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + return nil, err + } versioned := &v1alpha1.KubeletConfiguration{} - api.Scheme.Default(versioned) + scheme.Default(versioned) config := &kubeletconfig.KubeletConfiguration{} - if err := api.Scheme.Convert(versioned, config, nil); err != nil { + if err := scheme.Convert(versioned, config, nil); err != nil { return nil, err } return config, nil diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index 7761b2b768..85d37b75c6 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -60,6 +60,7 @@ import ( "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet" kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" "k8s.io/kubernetes/pkg/kubelet/cadvisor" "k8s.io/kubernetes/pkg/kubelet/certificate" @@ -188,20 +189,30 @@ func checkPermissions() error { return nil } -func setConfigz(cz *configz.Config, kc *kubeletconfiginternal.KubeletConfiguration) { - tmp := kubeletconfigv1alpha1.KubeletConfiguration{} - api.Scheme.Convert(kc, &tmp, nil) - cz.Set(tmp) +func setConfigz(cz *configz.Config, kc *kubeletconfiginternal.KubeletConfiguration) error { + scheme, _, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + return err + } + versioned := kubeletconfigv1alpha1.KubeletConfiguration{} + if err := scheme.Convert(kc, &versioned, nil); err != nil { + return err + } + cz.Set(versioned) + return nil } -func initConfigz(kc *kubeletconfiginternal.KubeletConfiguration) (*configz.Config, error) { +func initConfigz(kc *kubeletconfiginternal.KubeletConfiguration) error { cz, err := configz.New("kubeletconfig") - if err == nil { - setConfigz(cz, kc) - } else { + if err != nil { glog.Errorf("unable to register configz: %s", err) + return err } - return cz, err + if err := setConfigz(cz, kc); err != nil { + glog.Errorf("unable to register config: %s", err) + return err + } + return nil } // makeEventRecorder sets up kubeDeps.Recorder if its nil. Its a no-op otherwise. @@ -250,7 +261,7 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.Dependencies) (err error) { } // Register current configuration with /configz endpoint - _, err = initConfigz(&s.KubeletConfiguration) + err = initConfigz(&s.KubeletConfiguration) if err != nil { glog.Errorf("unable to register KubeletConfiguration with configz, error: %v", err) } @@ -756,7 +767,8 @@ func parseResourceList(m kubeletconfiginternal.ConfigurationMap) (v1.ResourceLis } // BootstrapKubeletConfigController constructs and bootstrap a configuration controller -func BootstrapKubeletConfigController(flags *options.KubeletFlags, +func BootstrapKubeletConfigController( + flags *options.KubeletFlags, defaultConfig *kubeletconfiginternal.KubeletConfiguration) (*kubeletconfiginternal.KubeletConfiguration, *kubeletconfig.Controller, error) { var err error // Alpha Dynamic Configuration Implementation; this section only loads config from disk, it does not contact the API server @@ -777,7 +789,10 @@ func BootstrapKubeletConfigController(flags *options.KubeletFlags, } // get the latest KubeletConfiguration checkpoint from disk, or load the init or default config if no valid checkpoints exist - kubeletConfigController := kubeletconfig.NewController(initConfigDir, dynamicConfigDir, defaultConfig) + kubeletConfigController, err := kubeletconfig.NewController(initConfigDir, dynamicConfigDir, defaultConfig) + if err != nil { + return nil, nil, fmt.Errorf("failed to construct controller, error: %v", err) + } kubeletConfig, err := kubeletConfigController.Bootstrap() if err != nil { return nil, nil, fmt.Errorf("failed to determine a valid configuration, error: %v", err) diff --git a/cmd/kubelet/kubelet.go b/cmd/kubelet/kubelet.go index d26d213e5e..f63526f5c2 100644 --- a/cmd/kubelet/kubelet.go +++ b/cmd/kubelet/kubelet.go @@ -89,7 +89,10 @@ func main() { } // construct a KubeletServer from kubeletFlags and kubeletConfig - kubeletServer := &options.KubeletServer{KubeletFlags: *kubeletFlags, KubeletConfiguration: *kubeletConfig} + kubeletServer := &options.KubeletServer{ + KubeletFlags: *kubeletFlags, + KubeletConfiguration: *kubeletConfig, + } // use kubeletServer to construct the default KubeletDeps kubeletDeps, err := app.UnsecuredDependencies(kubeletServer) diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index ff7ca6ee3d..f6e2daa85e 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -208,7 +208,6 @@ func TestRoundTripTypes(t *testing.T) { fuzzer := fuzzer.FuzzerFor(kapitesting.FuzzerFuncs, rand.NewSource(seed), api.Codecs) nonRoundTrippableTypes := map[schema.GroupVersionKind]bool{ - {Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeletConfiguration"}: true, {Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeProxyConfiguration"}: true, {Group: "componentconfig", Version: runtime.APIVersionInternal, Kind: "KubeSchedulerConfiguration"}: true, } diff --git a/pkg/kubelet/apis/kubeletconfig/BUILD b/pkg/kubelet/apis/kubeletconfig/BUILD index 92d82340d2..07c85a597d 100644 --- a/pkg/kubelet/apis/kubeletconfig/BUILD +++ b/pkg/kubelet/apis/kubeletconfig/BUILD @@ -36,7 +36,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", - "//pkg/kubelet/apis/kubeletconfig/install:all-srcs", + "//pkg/kubelet/apis/kubeletconfig/scheme:all-srcs", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:all-srcs", "//pkg/kubelet/apis/kubeletconfig/validation:all-srcs", ], diff --git a/pkg/kubelet/apis/kubeletconfig/install/install.go b/pkg/kubelet/apis/kubeletconfig/install/install.go deleted file mode 100644 index 731791f9d0..0000000000 --- a/pkg/kubelet/apis/kubeletconfig/install/install.go +++ /dev/null @@ -1,49 +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 install installs the experimental API group, making it available as -// an option to all of the API encoding/decoding machinery. -package install - -import ( - "k8s.io/apimachinery/pkg/apimachinery/announced" - "k8s.io/apimachinery/pkg/apimachinery/registered" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" - "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" -) - -func init() { - // TODO(mtaufen): probably want to create a kubelet scheme rather than reusing the api scheme, but need to ask lavalamp - Install(api.GroupFactoryRegistry, api.Registry, api.Scheme) -} - -// Install registers the API group and adds types to a scheme -func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { - if err := announced.NewGroupMetaFactory( - &announced.GroupMetaFactoryArgs{ - GroupName: kubeletconfig.GroupName, - VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version}, - AddInternalObjectsToScheme: kubeletconfig.AddToScheme, - }, - announced.VersionToSchemeFunc{ - v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme, - }, - ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { - panic(err) - } -} diff --git a/pkg/kubelet/apis/kubeletconfig/install/BUILD b/pkg/kubelet/apis/kubeletconfig/scheme/BUILD similarity index 55% rename from pkg/kubelet/apis/kubeletconfig/install/BUILD rename to pkg/kubelet/apis/kubeletconfig/scheme/BUILD index 111801fa0b..44660bf981 100644 --- a/pkg/kubelet/apis/kubeletconfig/install/BUILD +++ b/pkg/kubelet/apis/kubeletconfig/scheme/BUILD @@ -1,23 +1,14 @@ -package(default_visibility = ["//visibility:public"]) - -licenses(["notice"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) +load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["install.go"], - tags = ["automanaged"], + srcs = ["scheme.go"], + visibility = ["//visibility:public"], deps = [ - "//pkg/api:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", ], ) @@ -32,4 +23,5 @@ filegroup( name = "all-srcs", srcs = [":package-srcs"], tags = ["automanaged"], + visibility = ["//visibility:public"], ) diff --git a/pkg/kubelet/apis/kubeletconfig/scheme/scheme.go b/pkg/kubelet/apis/kubeletconfig/scheme/scheme.go new file mode 100644 index 0000000000..76f042818e --- /dev/null +++ b/pkg/kubelet/apis/kubeletconfig/scheme/scheme.go @@ -0,0 +1,40 @@ +/* +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 scheme + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" +) + +// Utility functions for the Kubelet's kubeletconfig API group + +// NewSchemeAndCodecs is a utility funciton that returns a Scheme and CodecFactory +// that understand the types in the kubeletconfig API group. +func NewSchemeAndCodecs() (*runtime.Scheme, *serializer.CodecFactory, error) { + scheme := runtime.NewScheme() + if err := kubeletconfig.AddToScheme(scheme); err != nil { + return nil, nil, err + } + if err := v1alpha1.AddToScheme(scheme); err != nil { + return nil, nil, err + } + codecs := serializer.NewCodecFactory(scheme) + return scheme, &codecs, nil +} diff --git a/pkg/kubelet/kubeletconfig/checkpoint/BUILD b/pkg/kubelet/kubeletconfig/checkpoint/BUILD index 8e2844f81c..c4d1fa6f88 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/BUILD +++ b/pkg/kubelet/kubeletconfig/checkpoint/BUILD @@ -15,8 +15,8 @@ go_test( ], library = ":go_default_library", deps = [ - "//pkg/api:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/kubeletconfig/util/codec:go_default_library", "//pkg/kubelet/kubeletconfig/util/test:go_default_library", @@ -40,12 +40,14 @@ go_library( deps = [ "//pkg/api:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/kubeletconfig/util/codec:go_default_library", "//pkg/kubelet/kubeletconfig/util/log:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) diff --git a/pkg/kubelet/kubeletconfig/checkpoint/checkpoint.go b/pkg/kubelet/kubeletconfig/checkpoint/checkpoint.go index e9a6c29d70..852ea71488 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/checkpoint.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/checkpoint.go @@ -30,7 +30,7 @@ import ( type Checkpoint interface { // UID returns the UID of the config source object behind the Checkpoint UID() string - // Parse parses the checkpoint into the internal KubeletConfiguration type + // Parse extracts the KubeletConfiguration from the checkpoint, applies defaults, and converts to the internal type Parse() (*kubeletconfig.KubeletConfiguration, error) // Encode returns a []byte representation of the config source object behind the Checkpoint Encode() ([]byte, error) @@ -56,6 +56,7 @@ func DecodeCheckpoint(data []byte) (Checkpoint, error) { if err != nil { return nil, fmt.Errorf("failed to convert decoded object into a v1 ConfigMap, error: %v", err) } + return NewConfigMapCheckpoint(cm) } diff --git a/pkg/kubelet/kubeletconfig/checkpoint/checkpoint_test.go b/pkg/kubelet/kubeletconfig/checkpoint/checkpoint_test.go index d9cc259524..abcf9981b1 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/checkpoint_test.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/checkpoint_test.go @@ -30,20 +30,6 @@ import ( utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test" ) -// newUnsupportedEncoded returns an encoding of an object that does not have a Checkpoint implementation -func newUnsupportedEncoded(t *testing.T) []byte { - encoder, err := utilcodec.NewJSONEncoder(apiv1.GroupName) - if err != nil { - t.Fatalf("could not create an encoder, error: %v", err) - } - unsupported := &apiv1.Node{} - data, err := runtime.Encode(encoder, unsupported) - if err != nil { - t.Fatalf("could not encode object, error: %v", err) - } - return data -} - func TestDecodeCheckpoint(t *testing.T) { // generate correct Checkpoint for v1/ConfigMap test case cm, err := NewConfigMapCheckpoint(&apiv1.ConfigMap{ObjectMeta: metav1.ObjectMeta{UID: types.UID("uid")}}) @@ -87,3 +73,17 @@ func TestDecodeCheckpoint(t *testing.T) { } } } + +// newUnsupportedEncoded returns an encoding of an object that does not have a Checkpoint implementation +func newUnsupportedEncoded(t *testing.T) []byte { + encoder, err := utilcodec.NewJSONEncoder(apiv1.GroupName) + if err != nil { + t.Fatalf("could not create an encoder, error: %v", err) + } + unsupported := &apiv1.Node{} + data, err := runtime.Encode(encoder, unsupported) + if err != nil { + t.Fatalf("could not encode object, error: %v", err) + } + return data +} diff --git a/pkg/kubelet/kubeletconfig/checkpoint/configmap.go b/pkg/kubelet/kubeletconfig/checkpoint/configmap.go index b0a8ecdea9..7472384889 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/configmap.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/configmap.go @@ -21,7 +21,9 @@ import ( apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec" ) @@ -29,7 +31,8 @@ const configMapConfigKey = "kubelet" // configMapCheckpoint implements Checkpoint, backed by a v1/ConfigMap config source object type configMapCheckpoint struct { - configMap *apiv1.ConfigMap + kubeletCodecs *serializer.CodecFactory // codecs for the KubeletConfiguration + configMap *apiv1.ConfigMap } // NewConfigMapCheckpoint returns a Checkpoint backed by `cm`. `cm` must be non-nil @@ -40,7 +43,13 @@ func NewConfigMapCheckpoint(cm *apiv1.ConfigMap) (Checkpoint, error) { } else if len(cm.ObjectMeta.UID) == 0 { return nil, fmt.Errorf("ConfigMap must have a UID to be treated as a Checkpoint") } - return &configMapCheckpoint{cm}, nil + + _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + return nil, err + } + + return &configMapCheckpoint{kubeletCodecs, cm}, nil } // UID returns the UID of a configMapCheckpoint @@ -48,25 +57,23 @@ func (c *configMapCheckpoint) UID() string { return string(c.configMap.UID) } -// implements Parse for v1/ConfigMap checkpoints +// Parse extracts the KubeletConfiguration from v1/ConfigMap checkpoints, applies defaults, and converts to the internal type func (c *configMapCheckpoint) Parse() (*kubeletconfig.KubeletConfiguration, error) { const emptyCfgErr = "config was empty, but some parameters are required" - cm := c.configMap - - if len(cm.Data) == 0 { + if len(c.configMap.Data) == 0 { return nil, fmt.Errorf(emptyCfgErr) } // TODO(mtaufen): Once the KubeletConfiguration type is decomposed, extend this to a key for each sub-object - config, ok := cm.Data[configMapConfigKey] + config, ok := c.configMap.Data[configMapConfigKey] if !ok { return nil, fmt.Errorf("key %q not found in ConfigMap", configMapConfigKey) } else if len(config) == 0 { return nil, fmt.Errorf(emptyCfgErr) } - return utilcodec.DecodeKubeletConfiguration([]byte(config)) + return utilcodec.DecodeKubeletConfiguration(c.kubeletCodecs, []byte(config)) } // Encode encodes a configMapCheckpoint diff --git a/pkg/kubelet/kubeletconfig/checkpoint/configmap_test.go b/pkg/kubelet/kubeletconfig/checkpoint/configmap_test.go index 07092e4446..98d8a37eb2 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/configmap_test.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/configmap_test.go @@ -26,8 +26,8 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test" ) @@ -66,9 +66,15 @@ func TestNewConfigMapCheckpoint(t *testing.T) { } func TestConfigMapCheckpointUID(t *testing.T) { + _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + cases := []string{"", "uid", "376dfb73-56db-11e7-a01e-42010a800002"} for _, uidIn := range cases { cpt := &configMapCheckpoint{ + kubeletCodecs, &apiv1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{UID: types.UID(uidIn)}, }, @@ -82,11 +88,16 @@ func TestConfigMapCheckpointUID(t *testing.T) { } func TestConfigMapCheckpointParse(t *testing.T) { + kubeletScheme, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // get the built-in default configuration external := &kubeletconfigv1alpha1.KubeletConfiguration{} - api.Scheme.Default(external) + kubeletScheme.Default(external) defaultConfig := &kubeletconfig.KubeletConfiguration{} - err := api.Scheme.Convert(external, defaultConfig, nil) + err = kubeletScheme.Convert(external, defaultConfig, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -123,7 +134,7 @@ apiVersion: kubeletconfig/v1alpha1`}}, defaultConfig, ""}, "kubelet": `{"kind":"KubeletConfiguration","apiVersion":"kubeletconfig/v1alpha1"}`}}, defaultConfig, ""}, } for _, c := range cases { - cpt := &configMapCheckpoint{c.cm} + cpt := &configMapCheckpoint{kubeletCodecs, c.cm} kc, err := cpt.Parse() if utiltest.SkipRest(t, c.desc, err, c.err) { continue @@ -136,6 +147,11 @@ apiVersion: kubeletconfig/v1alpha1`}}, defaultConfig, ""}, } func TestConfigMapCheckpointEncode(t *testing.T) { + _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // only one case, based on output from the existing encoder, and since // this is hard to test (key order isn't guaranteed), we should probably // just stick to this test case and mostly rely on the round-trip test. @@ -146,7 +162,7 @@ func TestConfigMapCheckpointEncode(t *testing.T) { }{ // we expect Checkpoints to be encoded as a json representation of the underlying API object {"one-key", - &configMapCheckpoint{&apiv1.ConfigMap{ + &configMapCheckpoint{kubeletCodecs, &apiv1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{Name: "one-key"}, Data: map[string]string{"one": ""}}}, `{"kind":"ConfigMap","apiVersion":"v1","metadata":{"name":"one-key","creationTimestamp":null},"data":{"one":""}} @@ -166,6 +182,11 @@ func TestConfigMapCheckpointEncode(t *testing.T) { } func TestConfigMapCheckpointRoundTrip(t *testing.T) { + _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + cases := []struct { desc string cpt *configMapCheckpoint @@ -173,7 +194,7 @@ func TestConfigMapCheckpointRoundTrip(t *testing.T) { }{ // empty data {"empty data", - &configMapCheckpoint{&apiv1.ConfigMap{ + &configMapCheckpoint{kubeletCodecs, &apiv1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "empty-data-sha256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", UID: "uid", @@ -182,7 +203,7 @@ func TestConfigMapCheckpointRoundTrip(t *testing.T) { ""}, // two keys {"two keys", - &configMapCheckpoint{&apiv1.ConfigMap{ + &configMapCheckpoint{kubeletCodecs, &apiv1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "two-keys-sha256-2bff03d6249c8a9dc9a1436d087c124741361ccfac6615b81b67afcff5c42431", UID: "uid", @@ -191,7 +212,7 @@ func TestConfigMapCheckpointRoundTrip(t *testing.T) { ""}, // missing uid {"missing uid", - &configMapCheckpoint{&apiv1.ConfigMap{ + &configMapCheckpoint{kubeletCodecs, &apiv1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "two-keys-sha256-2bff03d6249c8a9dc9a1436d087c124741361ccfac6615b81b67afcff5c42431", UID: "", diff --git a/pkg/kubelet/kubeletconfig/checkpoint/download.go b/pkg/kubelet/kubeletconfig/checkpoint/download.go index b61b394ab4..14374a4cbf 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/download.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/download.go @@ -113,7 +113,6 @@ func (r *remoteConfigMap) UID() string { func (r *remoteConfigMap) Download(client clientset.Interface) (Checkpoint, string, error) { var reason string - uid := string(r.source.ConfigMapRef.UID) utillog.Infof("attempting to download ConfigMap with UID %q", uid) @@ -131,8 +130,14 @@ func (r *remoteConfigMap) Download(client clientset.Interface) (Checkpoint, stri return nil, reason, fmt.Errorf(reason) } + checkpoint, err := NewConfigMapCheckpoint(cm) + if err != nil { + reason = fmt.Sprintf("invalid downloaded object") + return nil, reason, fmt.Errorf("%s, error: %v", reason, err) + } + utillog.Infof("successfully downloaded ConfigMap with UID %q", uid) - return &configMapCheckpoint{cm}, "", nil + return checkpoint, "", nil } func (r *remoteConfigMap) Encode() ([]byte, error) { diff --git a/pkg/kubelet/kubeletconfig/checkpoint/download_test.go b/pkg/kubelet/kubeletconfig/checkpoint/download_test.go index 496353919f..da3f60e343 100644 --- a/pkg/kubelet/kubeletconfig/checkpoint/download_test.go +++ b/pkg/kubelet/kubeletconfig/checkpoint/download_test.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" fakeclient "k8s.io/client-go/kubernetes/fake" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test" ) @@ -92,6 +93,11 @@ func TestRemoteConfigMapUID(t *testing.T) { } func TestRemoteConfigMapDownload(t *testing.T) { + _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + cm := &apiv1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "name", @@ -118,7 +124,7 @@ func TestRemoteConfigMapDownload(t *testing.T) { // successful download {"object exists and reference is correct", &remoteConfigMap{&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}}}, - &configMapCheckpoint{cm}, ""}, + &configMapCheckpoint{kubeletCodecs, cm}, ""}, } for _, c := range cases { diff --git a/pkg/kubelet/kubeletconfig/configfiles/BUILD b/pkg/kubelet/kubeletconfig/configfiles/BUILD index 94a32a7191..2884d0cfbf 100644 --- a/pkg/kubelet/kubeletconfig/configfiles/BUILD +++ b/pkg/kubelet/kubeletconfig/configfiles/BUILD @@ -10,8 +10,10 @@ go_library( srcs = ["configfiles.go"], deps = [ "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/kubeletconfig/util/codec:go_default_library", "//pkg/kubelet/kubeletconfig/util/filesystem:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", ], ) diff --git a/pkg/kubelet/kubeletconfig/configfiles/configfiles.go b/pkg/kubelet/kubeletconfig/configfiles/configfiles.go index 449c5c8112..c3ad50e22f 100644 --- a/pkg/kubelet/kubeletconfig/configfiles/configfiles.go +++ b/pkg/kubelet/kubeletconfig/configfiles/configfiles.go @@ -20,7 +20,9 @@ import ( "fmt" "path/filepath" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec" utilfs "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/filesystem" ) @@ -35,16 +37,24 @@ type Loader interface { type fsLoader struct { // fs is the filesystem where the config files exist; can be mocked for testing fs utilfs.Filesystem + // kubeletCodecs is the scheme used to decode config files + kubeletCodecs *serializer.CodecFactory // configDir is the absolute path to the directory containing the configuration files configDir string } // NewFSLoader returns a Loader that loads a KubeletConfiguration from the files in `configDir` -func NewFSLoader(fs utilfs.Filesystem, configDir string) Loader { - return &fsLoader{ - fs: fs, - configDir: configDir, +func NewFSLoader(fs utilfs.Filesystem, configDir string) (Loader, error) { + _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + return nil, err } + + return &fsLoader{ + fs: fs, + kubeletCodecs: kubeletCodecs, + configDir: configDir, + }, nil } func (loader *fsLoader) Load() (*kubeletconfig.KubeletConfiguration, error) { @@ -62,5 +72,5 @@ func (loader *fsLoader) Load() (*kubeletconfig.KubeletConfiguration, error) { return nil, fmt.Errorf(errfmt, fmt.Errorf("config file was empty, but some parameters are required")) } - return utilcodec.DecodeKubeletConfiguration(data) + return utilcodec.DecodeKubeletConfiguration(loader.kubeletCodecs, data) } diff --git a/pkg/kubelet/kubeletconfig/controller.go b/pkg/kubelet/kubeletconfig/controller.go index f6f0c34e6d..afee768085 100644 --- a/pkg/kubelet/kubeletconfig/controller.go +++ b/pkg/kubelet/kubeletconfig/controller.go @@ -53,7 +53,7 @@ const ( // - validates configuration // - monitors for potential crash-loops caused by new configurations // - tracks the last-known-good configuration, and rolls-back to last-known-good when necessary -// For more information, see the proposal: https://github.com/kubernetes/kubernetes/pull/29459 +// For more information, see the proposal: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/dynamic-kubelet-configuration.md type Controller struct { // dynamicConfig, if true, indicates that we should sync config from the API server dynamicConfig bool @@ -90,12 +90,19 @@ type Controller struct { // If the `initConfigDir` is an empty string, skips trying to load the init config. // If the `dynamicConfigDir` is an empty string, skips trying to load checkpoints or download new config, // but will still sync the ConfigOK condition if you call StartSync with a non-nil client. -func NewController(initConfigDir string, dynamicConfigDir string, defaultConfig *kubeletconfig.KubeletConfiguration) *Controller { +func NewController(initConfigDir string, + dynamicConfigDir string, + defaultConfig *kubeletconfig.KubeletConfiguration) (*Controller, error) { + var err error + fs := utilfs.DefaultFs{} var initLoader configfiles.Loader if len(initConfigDir) > 0 { - initLoader = configfiles.NewFSLoader(fs, initConfigDir) + initLoader, err = configfiles.NewFSLoader(fs, initConfigDir) + if err != nil { + return nil, err + } } dynamicConfig := false if len(dynamicConfigDir) > 0 { @@ -120,7 +127,7 @@ func NewController(initConfigDir string, dynamicConfigDir string, defaultConfig badConfigTracker: badconfig.NewFsTracker(fs, filepath.Join(dynamicConfigDir, badConfigTrackingDir, kubeletVersion)), startupTracker: startups.NewFsTracker(fs, filepath.Join(dynamicConfigDir, startupTrackingDir, kubeletVersion)), initLoader: initLoader, - } + }, nil } // Bootstrap attempts to return a valid KubeletConfiguration based on the configuration of the Controller, diff --git a/pkg/kubelet/kubeletconfig/util/codec/BUILD b/pkg/kubelet/kubeletconfig/util/codec/BUILD index 9a55c12caf..df4916b26c 100644 --- a/pkg/kubelet/kubeletconfig/util/codec/BUILD +++ b/pkg/kubelet/kubeletconfig/util/codec/BUILD @@ -12,9 +12,8 @@ go_library( "//pkg/api:go_default_library", "//pkg/api/install:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", - "//pkg/kubelet/apis/kubeletconfig/install:go_default_library", - "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", ], ) diff --git a/pkg/kubelet/kubeletconfig/util/codec/codec.go b/pkg/kubelet/kubeletconfig/util/codec/codec.go index 14432dae6e..148967caba 100644 --- a/pkg/kubelet/kubeletconfig/util/codec/codec.go +++ b/pkg/kubelet/kubeletconfig/util/codec/codec.go @@ -21,13 +21,11 @@ import ( // ensure the core apis are installed _ "k8s.io/kubernetes/pkg/api/install" - // ensure the kubeletconfig apis are installed - _ "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/install" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" - kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" ) // TODO(mtaufen): allow an encoder to be injected into checkpoint objects at creation time? (then we could ultimately instantiate only one encoder) @@ -50,28 +48,18 @@ func NewJSONEncoder(groupName string) (runtime.Encoder, error) { return api.Codecs.EncoderForVersion(info.Serializer, versions[0]), nil } -// DecodeKubeletConfiguration decodes an encoded (v1alpha1) KubeletConfiguration object to the internal type -func DecodeKubeletConfiguration(data []byte) (*kubeletconfig.KubeletConfiguration, error) { - // decode the object, note we use the external version scheme to decode, because users provide the external version - obj, err := runtime.Decode(api.Codecs.UniversalDecoder(kubeletconfigv1alpha1.SchemeGroupVersion), data) +// 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 + obj, gvk, err := kubeletCodecs.UniversalDecoder().Decode(data, nil, nil) if err != nil { return nil, fmt.Errorf("failed to decode, error: %v", err) } - externalKC, ok := obj.(*kubeletconfigv1alpha1.KubeletConfiguration) + internalKC, ok := obj.(*kubeletconfig.KubeletConfiguration) if !ok { - return nil, fmt.Errorf("failed to cast object to KubeletConfiguration, object: %#v", obj) + return nil, fmt.Errorf("failed to cast object to KubeletConfiguration, unexpected type: %v", gvk) } - // TODO(mtaufen): confirm whether api.Codecs.UniversalDecoder runs the defaulting, which would make this redundant - // run the defaulter on the decoded configuration before converting to internal type - api.Scheme.Default(externalKC) - - // convert to internal type - internalKC := &kubeletconfig.KubeletConfiguration{} - err = api.Scheme.Convert(externalKC, internalKC, nil) - if err != nil { - return nil, err - } return internalKC, nil } diff --git a/pkg/kubemark/BUILD b/pkg/kubemark/BUILD index 457927addf..080c853501 100644 --- a/pkg/kubemark/BUILD +++ b/pkg/kubemark/BUILD @@ -21,7 +21,6 @@ go_library( "//pkg/controller:go_default_library", "//pkg/kubelet:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", - "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/cadvisor:go_default_library", "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/container/testing:go_default_library", diff --git a/pkg/kubemark/hollow_kubelet.go b/pkg/kubemark/hollow_kubelet.go index b5fa78188c..b81131ee62 100644 --- a/pkg/kubemark/hollow_kubelet.go +++ b/pkg/kubemark/hollow_kubelet.go @@ -22,10 +22,8 @@ import ( clientset "k8s.io/client-go/kubernetes" kubeletapp "k8s.io/kubernetes/cmd/kubelet/app" "k8s.io/kubernetes/cmd/kubelet/app/options" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubelet" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" - "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" "k8s.io/kubernetes/pkg/kubelet/cadvisor" "k8s.io/kubernetes/pkg/kubelet/cm" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" @@ -117,12 +115,10 @@ func GetHollowKubeletConfig( } // Config struct - // Do the external -> internal conversion to make sure that defaults - // are set for fields not overridden in NewHollowKubelet. - tmp := &v1alpha1.KubeletConfiguration{} - api.Scheme.Default(tmp) - c := &kubeletconfig.KubeletConfiguration{} - api.Scheme.Convert(tmp, c, nil) + c, err := options.NewKubeletConfiguration() + if err != nil { + panic(err) + } c.ManifestURL = "" c.Address = "0.0.0.0" /* bind address */ diff --git a/test/e2e_node/BUILD b/test/e2e_node/BUILD index fad63ad1cb..c4369ae830 100644 --- a/test/e2e_node/BUILD +++ b/test/e2e_node/BUILD @@ -25,11 +25,11 @@ go_library( "//conditions:default": [], }), deps = [ - "//pkg/api:go_default_library", "//pkg/api/v1/pod:go_default_library", "//pkg/kubelet/apis/cri:go_default_library", "//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", "//pkg/kubelet/metrics:go_default_library", diff --git a/test/e2e_node/util.go b/test/e2e_node/util.go index 3cbca9dd85..4ccc916cf3 100644 --- a/test/e2e_node/util.go +++ b/test/e2e_node/util.go @@ -35,8 +35,8 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/kubernetes/scheme" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" stats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" kubeletmetrics "k8s.io/kubernetes/pkg/kubelet/metrics" @@ -259,13 +259,17 @@ func createConfigMap(f *framework.Framework, internalKC *kubeletconfig.KubeletCo // constructs a ConfigMap, populating one of its keys with the KubeletConfiguration. Uses GenerateName. func makeKubeletConfigMap(internalKC *kubeletconfig.KubeletConfiguration) *v1.ConfigMap { - externalKC := &kubeletconfigv1alpha1.KubeletConfiguration{} - api.Scheme.Convert(internalKC, externalKC, nil) - - encoder, err := newJSONEncoder(kubeletconfig.GroupName) + scheme, _, err := kubeletscheme.NewSchemeAndCodecs() framework.ExpectNoError(err) - data, err := runtime.Encode(encoder, externalKC) + versioned := &kubeletconfigv1alpha1.KubeletConfiguration{} + err = scheme.Convert(internalKC, versioned, nil) + framework.ExpectNoError(err) + + encoder, err := newKubeletConfigJSONEncoder() + framework.ExpectNoError(err) + + data, err := runtime.Encode(encoder, versioned) framework.ExpectNoError(err) cmap := &v1.ConfigMap{ @@ -274,7 +278,6 @@ func makeKubeletConfigMap(internalKC *kubeletconfig.KubeletConfiguration) *v1.Co "kubelet": string(data), }, } - return cmap } @@ -310,21 +313,18 @@ func logKubeletMetrics(metricKeys ...string) { } } -func newJSONEncoder(groupName string) (runtime.Encoder, error) { - // encode to json +func newKubeletConfigJSONEncoder() (runtime.Encoder, error) { + _, kubeletCodecs, err := kubeletscheme.NewSchemeAndCodecs() + if err != nil { + return nil, err + } + mediaType := "application/json" - info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType) + info, ok := runtime.SerializerInfoForMediaType(kubeletCodecs.SupportedMediaTypes(), mediaType) if !ok { return nil, fmt.Errorf("unsupported media type %q", mediaType) } - - versions := api.Registry.EnabledVersionsForGroup(groupName) - if len(versions) == 0 { - return nil, fmt.Errorf("no enabled versions for group %q", groupName) - } - - // the "best" version supposedly comes first in the list returned from api.Registry.EnabledVersionsForGroup - return api.Codecs.EncoderForVersion(info.Serializer, versions[0]), nil + return kubeletCodecs.EncoderForVersion(info.Serializer, kubeletconfigv1alpha1.SchemeGroupVersion), nil } // runCommand runs the cmd and returns the combined stdout and stderr, or an diff --git a/test/integration/etcd/etcd_storage_path_test.go b/test/integration/etcd/etcd_storage_path_test.go index b3462acdb0..d94be2fe09 100644 --- a/test/integration/etcd/etcd_storage_path_test.go +++ b/test/integration/etcd/etcd_storage_path_test.go @@ -434,10 +434,6 @@ var ephemeralWhiteList = createEphemeralWhiteList( gvr("componentconfig", "v1alpha1", "kubeproxyconfigurations"), // not stored in etcd // -- - // k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1 - gvr("kubeletconfig", "v1alpha1", "kubeletconfigurations"), // not stored in etcd - // -- - // k8s.io/kubernetes/pkg/apis/extensions/v1beta1 gvr("extensions", "v1beta1", "deploymentrollbacks"), // used to rollback deployment, not stored in etcd gvr("extensions", "v1beta1", "replicationcontrollerdummies"), // not stored in etcd