
Over the course of recent development of the `componentconfigs` package, it became evident that most of the tests in this package cannot be implemented without using a component config. As all of the currently supported component configs are external to the kubeadm project (kubelet and kube-proxy), practically all of the tests in this package are now dependent on external code. This is not desirable, because other component's configs may change frequently and without much of a notice. In particular many configs add new fields without bumping their versions. In addition to that, some components may be deprecated in the future and many tests may use their configs as a place holder of a component config just to test some common functionality. To top that, there are many tests that test the same common functionality several times (for each different component config). Thus a kubeadm managed replacement and a fake test environment are introduced. The new test environment uses kubeadm's very own `ClusterConfiguration`. ClusterConfiguration is normally not managed by the `componentconfigs` package. It's only used, because of the following: - It's a versioned API that is under the control of kubeadm maintainers. This enables us to test the componentconfigs package more thoroughly without having to have full and always up to date knowledge about the config of another component. - Other components often introduce new fields in their configs without bumping up the config version. This, often times, requires that the PR that introduces such new fields to touch kubeadm test code. Doing so, requires more work on the part of developers and reviewers. When kubeadm moves out of k/k this would allow for more sporadic breaks in kubeadm tests as PRs that merge in k/k and introduce new fields won't be able to fix the tests in kubeadm. - If we implement tests for all common functionality using the config of another component and it gets deprecated and/or we stop supporting it in production, we'll have to focus on a massive test refactoring or just continue importing this config just for test use. Thus, to reduce maintenance costs without sacrificing test coverage, we introduce this mini-framework and set of tests here which replace the normal component configs with a single one (`ClusterConfiguration`) and test the component config independent logic of this package. As a result of this, many of the older test cases are refactored and greatly simplified to reflect on the new change as well. The old tests that are strictly tied to specific component configs (like the defaulting tests) are left unchanged. Signed-off-by: Rostislav M. Georgiev <rostislavg@vmware.com>
330 lines
12 KiB
Go
330 lines
12 KiB
Go
/*
|
|
Copyright 2019 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 componentconfigs
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/lithammer/dedent"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
clientsetfake "k8s.io/client-go/kubernetes/fake"
|
|
kubeletconfig "k8s.io/kubelet/config/v1beta1"
|
|
utilpointer "k8s.io/utils/pointer"
|
|
|
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
|
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
|
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
|
)
|
|
|
|
func testKubeletConfigMap(contents string) *v1.ConfigMap {
|
|
return &v1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
|
|
Namespace: metav1.NamespaceSystem,
|
|
},
|
|
Data: map[string]string{
|
|
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(contents),
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestKubeletDefault(t *testing.T) {
|
|
var resolverConfig string
|
|
if isSystemdResolvedActive, _ := isServiceActive("systemd-resolved"); isSystemdResolvedActive {
|
|
// If systemd-resolved is active, we need to set the default resolver config
|
|
resolverConfig = kubeletSystemdResolverConfig
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
clusterCfg kubeadmapi.ClusterConfiguration
|
|
expected kubeletConfig
|
|
}{
|
|
{
|
|
name: "No specific defaulting works",
|
|
clusterCfg: kubeadmapi.ClusterConfiguration{},
|
|
expected: kubeletConfig{
|
|
config: kubeletconfig.KubeletConfiguration{
|
|
FeatureGates: map[string]bool{},
|
|
StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
|
|
ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP},
|
|
Authentication: kubeletconfig.KubeletAuthentication{
|
|
X509: kubeletconfig.KubeletX509Authentication{
|
|
ClientCAFile: constants.CACertName,
|
|
},
|
|
Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
|
|
},
|
|
Webhook: kubeletconfig.KubeletWebhookAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
|
|
},
|
|
},
|
|
Authorization: kubeletconfig.KubeletAuthorization{
|
|
Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
|
|
},
|
|
HealthzBindAddress: kubeletHealthzBindAddress,
|
|
HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
|
|
RotateCertificates: kubeletRotateCertificates,
|
|
ResolverConfig: resolverConfig,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Service subnet, no dual stack defaulting works",
|
|
clusterCfg: kubeadmapi.ClusterConfiguration{
|
|
Networking: kubeadmapi.Networking{
|
|
ServiceSubnet: "192.168.0.0/16",
|
|
},
|
|
},
|
|
expected: kubeletConfig{
|
|
config: kubeletconfig.KubeletConfiguration{
|
|
FeatureGates: map[string]bool{},
|
|
StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
|
|
ClusterDNS: []string{"192.168.0.10"},
|
|
Authentication: kubeletconfig.KubeletAuthentication{
|
|
X509: kubeletconfig.KubeletX509Authentication{
|
|
ClientCAFile: constants.CACertName,
|
|
},
|
|
Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
|
|
},
|
|
Webhook: kubeletconfig.KubeletWebhookAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
|
|
},
|
|
},
|
|
Authorization: kubeletconfig.KubeletAuthorization{
|
|
Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
|
|
},
|
|
HealthzBindAddress: kubeletHealthzBindAddress,
|
|
HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
|
|
RotateCertificates: kubeletRotateCertificates,
|
|
ResolverConfig: resolverConfig,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Service subnet, explicitly disabled dual stack defaulting works",
|
|
clusterCfg: kubeadmapi.ClusterConfiguration{
|
|
FeatureGates: map[string]bool{
|
|
features.IPv6DualStack: false,
|
|
},
|
|
Networking: kubeadmapi.Networking{
|
|
ServiceSubnet: "192.168.0.0/16",
|
|
},
|
|
},
|
|
expected: kubeletConfig{
|
|
config: kubeletconfig.KubeletConfiguration{
|
|
FeatureGates: map[string]bool{
|
|
features.IPv6DualStack: false,
|
|
},
|
|
StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
|
|
ClusterDNS: []string{"192.168.0.10"},
|
|
Authentication: kubeletconfig.KubeletAuthentication{
|
|
X509: kubeletconfig.KubeletX509Authentication{
|
|
ClientCAFile: constants.CACertName,
|
|
},
|
|
Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
|
|
},
|
|
Webhook: kubeletconfig.KubeletWebhookAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
|
|
},
|
|
},
|
|
Authorization: kubeletconfig.KubeletAuthorization{
|
|
Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
|
|
},
|
|
HealthzBindAddress: kubeletHealthzBindAddress,
|
|
HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
|
|
RotateCertificates: kubeletRotateCertificates,
|
|
ResolverConfig: resolverConfig,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Service subnet, enabled dual stack defaulting works",
|
|
clusterCfg: kubeadmapi.ClusterConfiguration{
|
|
FeatureGates: map[string]bool{
|
|
features.IPv6DualStack: true,
|
|
},
|
|
Networking: kubeadmapi.Networking{
|
|
ServiceSubnet: "192.168.0.0/16",
|
|
},
|
|
},
|
|
expected: kubeletConfig{
|
|
config: kubeletconfig.KubeletConfiguration{
|
|
FeatureGates: map[string]bool{
|
|
features.IPv6DualStack: true,
|
|
},
|
|
StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
|
|
ClusterDNS: []string{"192.168.0.10"},
|
|
Authentication: kubeletconfig.KubeletAuthentication{
|
|
X509: kubeletconfig.KubeletX509Authentication{
|
|
ClientCAFile: constants.CACertName,
|
|
},
|
|
Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
|
|
},
|
|
Webhook: kubeletconfig.KubeletWebhookAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
|
|
},
|
|
},
|
|
Authorization: kubeletconfig.KubeletAuthorization{
|
|
Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
|
|
},
|
|
HealthzBindAddress: kubeletHealthzBindAddress,
|
|
HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
|
|
RotateCertificates: kubeletRotateCertificates,
|
|
ResolverConfig: resolverConfig,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "DNS domain defaulting works",
|
|
clusterCfg: kubeadmapi.ClusterConfiguration{
|
|
Networking: kubeadmapi.Networking{
|
|
DNSDomain: "example.com",
|
|
},
|
|
},
|
|
expected: kubeletConfig{
|
|
config: kubeletconfig.KubeletConfiguration{
|
|
FeatureGates: map[string]bool{},
|
|
StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
|
|
ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP},
|
|
ClusterDomain: "example.com",
|
|
Authentication: kubeletconfig.KubeletAuthentication{
|
|
X509: kubeletconfig.KubeletX509Authentication{
|
|
ClientCAFile: constants.CACertName,
|
|
},
|
|
Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
|
|
},
|
|
Webhook: kubeletconfig.KubeletWebhookAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
|
|
},
|
|
},
|
|
Authorization: kubeletconfig.KubeletAuthorization{
|
|
Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
|
|
},
|
|
HealthzBindAddress: kubeletHealthzBindAddress,
|
|
HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
|
|
RotateCertificates: kubeletRotateCertificates,
|
|
ResolverConfig: resolverConfig,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "CertificatesDir defaulting works",
|
|
clusterCfg: kubeadmapi.ClusterConfiguration{
|
|
CertificatesDir: "/path/to/certs",
|
|
},
|
|
expected: kubeletConfig{
|
|
config: kubeletconfig.KubeletConfiguration{
|
|
FeatureGates: map[string]bool{},
|
|
StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
|
|
ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP},
|
|
Authentication: kubeletconfig.KubeletAuthentication{
|
|
X509: kubeletconfig.KubeletX509Authentication{
|
|
ClientCAFile: filepath.Join("/path/to/certs", constants.CACertName),
|
|
},
|
|
Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
|
|
},
|
|
Webhook: kubeletconfig.KubeletWebhookAuthentication{
|
|
Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
|
|
},
|
|
},
|
|
Authorization: kubeletconfig.KubeletAuthorization{
|
|
Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
|
|
},
|
|
HealthzBindAddress: kubeletHealthzBindAddress,
|
|
HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
|
|
RotateCertificates: kubeletRotateCertificates,
|
|
ResolverConfig: resolverConfig,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// This is the same for all test cases so we set it here
|
|
expected := test.expected
|
|
expected.configBase.GroupVersion = kubeletconfig.SchemeGroupVersion
|
|
|
|
got := &kubeletConfig{
|
|
configBase: configBase{
|
|
GroupVersion: kubeletconfig.SchemeGroupVersion,
|
|
},
|
|
}
|
|
got.Default(&test.clusterCfg, &kubeadmapi.APIEndpoint{}, &kubeadmapi.NodeRegistrationOptions{})
|
|
|
|
if !reflect.DeepEqual(got, &expected) {
|
|
t.Fatalf("Missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", expected, *got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// runKubeletFromTest holds common test case data and evaluation code for kubeletHandler.From* functions
|
|
func runKubeletFromTest(t *testing.T, perform func(gvk schema.GroupVersionKind, yaml string) (kubeadmapi.ComponentConfig, error)) {
|
|
const (
|
|
kind = "KubeletConfiguration"
|
|
clusterDomain = "foo.bar"
|
|
)
|
|
|
|
gvk := kubeletHandler.GroupVersion.WithKind(kind)
|
|
yaml := fmt.Sprintf("apiVersion: %s\nkind: %s\nclusterDomain: %s", kubeletHandler.GroupVersion, kind, clusterDomain)
|
|
|
|
cfg, err := perform(gvk, yaml)
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected failure: %v", err)
|
|
}
|
|
if cfg == nil {
|
|
t.Fatal("no config loaded where it should have been")
|
|
}
|
|
if kubeletCfg, ok := cfg.(*kubeletConfig); !ok {
|
|
t.Fatalf("found different object type than expected: %s", reflect.TypeOf(cfg))
|
|
} else if kubeletCfg.config.ClusterDomain != clusterDomain {
|
|
t.Fatalf("unexpected control value (clusterDomain):\n\tgot: %q\n\texpected: %q", kubeletCfg.config.ClusterDomain, clusterDomain)
|
|
}
|
|
}
|
|
|
|
func TestKubeletFromDocumentMap(t *testing.T) {
|
|
runKubeletFromTest(t, func(gvk schema.GroupVersionKind, yaml string) (kubeadmapi.ComponentConfig, error) {
|
|
return kubeletHandler.FromDocumentMap(kubeadmapi.DocumentMap{
|
|
gvk: []byte(yaml),
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestKubeletFromCluster(t *testing.T) {
|
|
runKubeletFromTest(t, func(_ schema.GroupVersionKind, yaml string) (kubeadmapi.ComponentConfig, error) {
|
|
client := clientsetfake.NewSimpleClientset(
|
|
testKubeletConfigMap(yaml),
|
|
)
|
|
return kubeletHandler.FromCluster(client, testClusterCfg())
|
|
})
|
|
}
|