kubeadM: allow conversion of TimeoutForControlPlane

v1beta3.ClusterConfiguration.APIServer.TimeoutForControlPlane
must be migrated to {Init|Join}Configuration.Timeouts.
.ControlPlaneComponentHealthCheck.

To achieve this sort of cross-Kind migration do the following:
- Use a temporary, thread-safe variable in timeoututils.go
- Make the order of GVKs in documentMapToInitConfiguration
deterministic.
This commit is contained in:
Lubomir I. Ivanov 2024-06-04 15:12:18 +03:00
parent 4af99cd676
commit 09078d4810
7 changed files with 123 additions and 31 deletions

View File

@ -89,9 +89,7 @@ func fuzzClusterConfiguration(obj *kubeadm.ClusterConfiguration, c fuzz.Continue
obj.CIImageRepository = "" // This fields doesn't exists in public API >> using default to get the roundtrip test pass
obj.KubernetesVersion = "qux"
obj.CIKubernetesVersion = "" // This fields doesn't exists in public API >> using default to get the roundtrip test pass
obj.APIServer.TimeoutForControlPlane = &metav1.Duration{
Duration: 0,
}
obj.APIServer.TimeoutForControlPlane = &metav1.Duration{}
obj.ControllerManager.ExtraEnvs = nil
obj.APIServer.ExtraEnvs = nil
obj.Scheduler.ExtraEnvs = nil

View File

@ -39,6 +39,12 @@ func SetDefaultTimeouts(t **Timeouts) {
var (
activeTimeouts *Timeouts = nil
timeoutMutex = &sync.RWMutex{}
// conversionTimeoutControlPlane is a variable used when converting the v1beta3 field
// ClusterConfiguration.APIServer.TimeoutForControlPlane to
// v1beta4 {Init|Join}Configuration.Timeouts.ControlPlaneComponentHealthCheck.
// TODO: remove this once v1beta3 is removed.
conversionTimeoutControlPlane *metav1.Duration
)
func init() {
@ -58,3 +64,19 @@ func SetActiveTimeouts(timeouts *Timeouts) {
activeTimeouts = timeouts.DeepCopy()
timeoutMutex.Unlock()
}
// TODO: remove these once v1beta3 is removed
// GetConversionTimeoutControlPlane returns conversionTimeoutControlPlane.
func GetConversionTimeoutControlPlane() *metav1.Duration {
timeoutMutex.RLock()
defer timeoutMutex.RUnlock()
return conversionTimeoutControlPlane
}
// SetConversionTimeoutControlPlane stores t into conversionTimeoutControlPlane.
func SetConversionTimeoutControlPlane(t *metav1.Duration) {
timeoutMutex.Lock()
conversionTimeoutControlPlane = t.DeepCopy()
timeoutMutex.Unlock()
}

View File

@ -77,6 +77,9 @@ func Convert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in *Cl
out.EncryptionAlgorithm = kubeadm.EncryptionAlgorithmRSA2048
out.CertificateValidityPeriod = &metav1.Duration{Duration: constants.CertificateValidityPeriod}
out.CACertificateValidityPeriod = &metav1.Duration{Duration: constants.CACertificateValidityPeriod}
if in.APIServer.TimeoutForControlPlane != nil && in.APIServer.TimeoutForControlPlane.Duration != 0 {
kubeadm.SetConversionTimeoutControlPlane(in.APIServer.TimeoutForControlPlane)
}
return autoConvert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in, out, s)
}

View File

@ -25,7 +25,12 @@ import (
// Convert_kubeadm_InitConfiguration_To_v1beta4_InitConfiguration converts a private InitConfiguration to a public InitConfiguration.
func Convert_kubeadm_InitConfiguration_To_v1beta4_InitConfiguration(in *kubeadm.InitConfiguration, out *InitConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_InitConfiguration_To_v1beta4_InitConfiguration(in, out, s)
err := autoConvert_kubeadm_InitConfiguration_To_v1beta4_InitConfiguration(in, out, s)
timeoutControlPlane := kubeadm.GetConversionTimeoutControlPlane() // Remove with v1beta3.
if timeoutControlPlane != nil {
out.Timeouts.ControlPlaneComponentHealthCheck = timeoutControlPlane
}
return err
}
// Convert_v1beta4_InitConfiguration_To_kubeadm_InitConfiguration converts a public InitConfiguration to a private InitConfiguration.
@ -46,8 +51,9 @@ func Convert_kubeadm_APIServer_To_v1beta4_APIServer(in *kubeadm.APIServer, out *
// Convert_v1beta4_ClusterConfiguration_To_kubeadm_ClusterConfiguration is required due to missing TimeoutForControlPlane in v1beta4
func Convert_v1beta4_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in *ClusterConfiguration, out *kubeadm.ClusterConfiguration, s conversion.Scope) error {
err := autoConvert_v1beta4_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in, out, s)
out.APIServer.TimeoutForControlPlane = &metav1.Duration{}
return autoConvert_v1beta4_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in, out, s)
return err
}
// Convert_v1beta4_JoinConfiguration_To_kubeadm_JoinConfiguration converts a public JoinConfiguration to a private JoinConfiguration.
@ -57,6 +63,16 @@ func Convert_v1beta4_JoinConfiguration_To_kubeadm_JoinConfiguration(in *JoinConf
return err
}
// Convert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration converts a private JoinConfinguration to a public JoinCOnfiguration.
func Convert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration(in *kubeadm.JoinConfiguration, out *JoinConfiguration, s conversion.Scope) error {
err := autoConvert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration(in, out, s)
timeoutControlPlane := kubeadm.GetConversionTimeoutControlPlane() // Remove with v1beta3.
if timeoutControlPlane != nil {
out.Timeouts.ControlPlaneComponentHealthCheck = timeoutControlPlane
}
return err
}
// Convert_kubeadm_Discovery_To_v1beta4_Discovery is required because there is no Discovery.Timeout in v1beta4
func Convert_kubeadm_Discovery_To_v1beta4_Discovery(in *kubeadm.Discovery, out *Discovery, s conversion.Scope) error {
return autoConvert_kubeadm_Discovery_To_v1beta4_Discovery(in, out, s)

View File

@ -159,11 +159,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.JoinConfiguration)(nil), (*JoinConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration(a.(*kubeadm.JoinConfiguration), b.(*JoinConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*JoinControlPlane)(nil), (*kubeadm.JoinControlPlane)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta4_JoinControlPlane_To_kubeadm_JoinControlPlane(a.(*JoinControlPlane), b.(*kubeadm.JoinControlPlane), scope)
}); err != nil {
@ -309,6 +304,11 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddConversionFunc((*kubeadm.JoinConfiguration)(nil), (*JoinConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration(a.(*kubeadm.JoinConfiguration), b.(*JoinConfiguration), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ClusterConfiguration)(nil), (*kubeadm.ClusterConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta4_ClusterConfiguration_To_kubeadm_ClusterConfiguration(a.(*ClusterConfiguration), b.(*kubeadm.ClusterConfiguration), scope)
}); err != nil {
@ -768,11 +768,6 @@ func autoConvert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration(in *kube
return nil
}
// Convert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration is an autogenerated conversion function.
func Convert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration(in *kubeadm.JoinConfiguration, out *JoinConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_JoinConfiguration_To_v1beta4_JoinConfiguration(in, out, s)
}
func autoConvert_v1beta4_JoinControlPlane_To_kubeadm_JoinControlPlane(in *JoinControlPlane, out *kubeadm.JoinControlPlane, s conversion.Scope) error {
if err := Convert_v1beta4_APIEndpoint_To_kubeadm_APIEndpoint(&in.LocalAPIEndpoint, &out.LocalAPIEndpoint, s); err != nil {
return err

View File

@ -441,11 +441,11 @@ func TestMigrateOldConfig(t *testing.T) {
}
}
// Test the migration of extra args from v1beta3 to v1beta4, as this is the only breaking change that is migrated.
// Another breaking change is the removal of ClusterConfiguration.TimeoutForControlPlane, but this field is not
// migrated to InitConfiguration.Timeouts.ControlPlaneComponentHealthCheck due to API machinery limitations.
// Remove this test once v1beta3 is removed.
func TestMigrateV1Beta3ExtraArgs(t *testing.T) {
// Test the migration of all breaking changes in v1beta4, marked as "MIGRATED" in the YAML below:
// - ExtraArgs
// - ClusterConfiguration.APIServer.TimeoutForControlPlane -> {Init|Join}Configuration.Timeout.ControlPlaneComponentHealthCheck
// - JoinConfiguration.Discovery.Timeout -> JoinConfiguration.Timeout.Discovery
func TestMigrateV1Beta3WithBreakingChanges(t *testing.T) {
var (
gv = kubeadmapiv1old.SchemeGroupVersion.String()
gvNew = kubeadmapiv1.SchemeGroupVersion.String()
@ -465,27 +465,45 @@ func TestMigrateV1Beta3ExtraArgs(t *testing.T) {
advertiseAddress: 1.2.3.4
bindPort: 6443
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
kubeletExtraArgs:
criSocket: unix:///some-socket-path
kubeletExtraArgs: # MIGRATED
foo: bar
name: node
---
apiServer:
timeoutForControlPlane: 2m0s ### note: this is note migrated!
extraArgs:
timeoutForControlPlane: 2m32s # MIGRATED
extraArgs: # MIGRATED
foo: bar
apiVersion: %[1]s
controllerManager:
extraArgs:
extraArgs: # MIGRATED
foo: bar
etcd:
local:
extraArgs:
extraArgs: # MIGRATED
foo: bar
kind: ClusterConfiguration
kubernetesVersion: v1.10.0
scheduler:
extraArgs:
extraArgs: # MIGRATED
foo: bar
---
apiVersion: %[1]s
kind: JoinConfiguration
nodeRegistration:
criSocket: unix:///some-socket-path
imagePullPolicy: IfNotPresent
kubeletExtraArgs: # MIGRATED
foo: baz
name: foo
taints: null
discovery:
bootstrapToken:
apiServerEndpoint: some-address:6443
token: abcdef.0123456789abcdef
unsafeSkipCAVerification: true
tlsBootstrapToken: abcdef.0123456789abcdef
timeout: 2m10s # MIGRATED
`, gv))
expectedOutput = dedent.Dedent(fmt.Sprintf(`
@ -503,7 +521,7 @@ func TestMigrateV1Beta3ExtraArgs(t *testing.T) {
advertiseAddress: 1.2.3.4
bindPort: 6443
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
criSocket: unix:///some-socket-path
imagePullPolicy: IfNotPresent
imagePullSerial: true
kubeletExtraArgs:
@ -514,7 +532,7 @@ func TestMigrateV1Beta3ExtraArgs(t *testing.T) {
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
timeouts:
controlPlaneComponentHealthCheck: 4m0s
controlPlaneComponentHealthCheck: 2m32s
discovery: 5m0s
etcdAPICall: 2m0s
kubeletHealthCheck: 4m0s
@ -545,7 +563,7 @@ func TestMigrateV1Beta3ExtraArgs(t *testing.T) {
value: bar
imageRepository: registry.k8s.io
kind: ClusterConfiguration
kubernetesVersion: v1.30.1
kubernetesVersion: v1.10.0
networking:
dnsDomain: cluster.local
serviceSubnet: 10.96.0.0/12
@ -554,6 +572,33 @@ func TestMigrateV1Beta3ExtraArgs(t *testing.T) {
extraArgs:
- name: foo
value: bar
---
apiVersion: %[1]s
caCertPath: /etc/kubernetes/pki/ca.crt
discovery:
bootstrapToken:
apiServerEndpoint: some-address:6443
token: abcdef.0123456789abcdef
unsafeSkipCAVerification: true
tlsBootstrapToken: abcdef.0123456789abcdef
kind: JoinConfiguration
nodeRegistration:
criSocket: unix:///some-socket-path
imagePullPolicy: IfNotPresent
imagePullSerial: true
kubeletExtraArgs:
- name: foo
value: baz
name: foo
taints: null
timeouts:
controlPlaneComponentHealthCheck: 2m32s
discovery: 2m10s
etcdAPICall: 2m0s
kubeletHealthCheck: 4m0s
kubernetesAPICall: 1m0s
tlsBootstrap: 5m0s
upgradeManifests: 5m0s
`, gvNew))
)

View File

@ -20,6 +20,7 @@ import (
"bytes"
"net"
"os"
"sort"
"strconv"
"strings"
@ -307,7 +308,19 @@ func documentMapToInitConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecat
var initcfg *kubeadmapi.InitConfiguration
var clustercfg *kubeadmapi.ClusterConfiguration
for gvk, fileContent := range gvkmap {
// Sort the GVKs deterministically by GVK string.
// This allows ClusterConfiguration to be decoded first.
gvks := make([]schema.GroupVersionKind, 0, len(gvkmap))
for gvk := range gvkmap {
gvks = append(gvks, gvk)
}
sort.Slice(gvks, func(i, j int) bool {
return gvks[i].String() < gvks[j].String()
})
for _, gvk := range gvks {
fileContent := gvkmap[gvk]
// first, check if this GVK is supported and possibly not deprecated
if err := validateSupportedVersion(gvk.GroupVersion(), allowDeprecated, allowExperimental); err != nil {
return nil, err