Merge pull request #88124 from rosti/kubeadm-cc-upgrade-plan

kubeadm upgrade plan: print a component config state table
This commit is contained in:
Kubernetes Prow Robot 2020-07-03 05:02:47 -07:00 committed by GitHub
commit a2aaae2dd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 687 additions and 12 deletions

View File

@ -48,6 +48,26 @@ type ComponentUpgradePlan struct {
NewVersion string
}
// ComponentConfigVersionState describes the current and desired version of a component config
type ComponentConfigVersionState struct {
// Group points to the Kubernetes API group that covers the config
Group string
// CurrentVersion is the currently active component config version
// NOTE: This can be empty in case the config was not found on the cluster or it was unsupported
// kubeadm generated version
CurrentVersion string
// PreferredVersion is the component config version that is currently preferred by kubeadm for use.
// NOTE: As of today, this is the only version supported by kubeadm.
PreferredVersion string
// ManualUpgradeRequired indicates if users need to manually upgrade their component config versions. This happens if
// the CurrentVersion of the config is user supplied (or modified) and no longer supported. Users should upgrade
// their component configs to PreferredVersion or any other supported component config version.
ManualUpgradeRequired bool
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// UpgradePlan represents information about upgrade plan for the output
@ -56,4 +76,6 @@ type UpgradePlan struct {
metav1.TypeMeta
Components []ComponentUpgradePlan
ConfigVersions []ComponentConfigVersionState
}

View File

@ -48,6 +48,26 @@ type ComponentUpgradePlan struct {
NewVersion string `json:"newVersion"`
}
// ComponentConfigVersionState describes the current and desired version of a component config
type ComponentConfigVersionState struct {
// Group points to the Kubernetes API group that covers the config
Group string `json:"group"`
// CurrentVersion is the currently active component config version
// NOTE: This can be empty in case the config was not found on the cluster or it was unsupported
// kubeadm generated version
CurrentVersion string `json:"currentVersion"`
// PreferredVersion is the component config version that is currently preferred by kubeadm for use.
// NOTE: As of today, this is the only version supported by kubeadm.
PreferredVersion string `json:"preferredVersion"`
// ManualUpgradeRequired indicates if users need to manually upgrade their component config versions. This happens if
// the CurrentVersion of the config is user supplied (or modified) and no longer supported. Users should upgrade
// their component configs to PreferredVersion or any other supported component config version.
ManualUpgradeRequired bool `json:"manualUpgradeRequired"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// UpgradePlan represents information about upgrade plan for the output
@ -56,4 +76,6 @@ type UpgradePlan struct {
metav1.TypeMeta
Components []ComponentUpgradePlan `json:"components"`
ConfigVersions []ComponentConfigVersionState `json:"configVersions"`
}

View File

@ -45,6 +45,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*ComponentConfigVersionState)(nil), (*output.ComponentConfigVersionState)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState(a.(*ComponentConfigVersionState), b.(*output.ComponentConfigVersionState), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*output.ComponentConfigVersionState)(nil), (*ComponentConfigVersionState)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState(a.(*output.ComponentConfigVersionState), b.(*ComponentConfigVersionState), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*ComponentUpgradePlan)(nil), (*output.ComponentUpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_ComponentUpgradePlan_To_output_ComponentUpgradePlan(a.(*ComponentUpgradePlan), b.(*output.ComponentUpgradePlan), scope)
}); err != nil {
@ -98,6 +108,32 @@ func Convert_output_BootstrapToken_To_v1alpha1_BootstrapToken(in *output.Bootstr
return autoConvert_output_BootstrapToken_To_v1alpha1_BootstrapToken(in, out, s)
}
func autoConvert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in *ComponentConfigVersionState, out *output.ComponentConfigVersionState, s conversion.Scope) error {
out.Group = in.Group
out.CurrentVersion = in.CurrentVersion
out.PreferredVersion = in.PreferredVersion
out.ManualUpgradeRequired = in.ManualUpgradeRequired
return nil
}
// Convert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState is an autogenerated conversion function.
func Convert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in *ComponentConfigVersionState, out *output.ComponentConfigVersionState, s conversion.Scope) error {
return autoConvert_v1alpha1_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in, out, s)
}
func autoConvert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState(in *output.ComponentConfigVersionState, out *ComponentConfigVersionState, s conversion.Scope) error {
out.Group = in.Group
out.CurrentVersion = in.CurrentVersion
out.PreferredVersion = in.PreferredVersion
out.ManualUpgradeRequired = in.ManualUpgradeRequired
return nil
}
// Convert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState is an autogenerated conversion function.
func Convert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState(in *output.ComponentConfigVersionState, out *ComponentConfigVersionState, s conversion.Scope) error {
return autoConvert_output_ComponentConfigVersionState_To_v1alpha1_ComponentConfigVersionState(in, out, s)
}
func autoConvert_v1alpha1_ComponentUpgradePlan_To_output_ComponentUpgradePlan(in *ComponentUpgradePlan, out *output.ComponentUpgradePlan, s conversion.Scope) error {
out.Name = in.Name
out.CurrentVersion = in.CurrentVersion
@ -144,6 +180,7 @@ func Convert_output_Images_To_v1alpha1_Images(in *output.Images, out *Images, s
func autoConvert_v1alpha1_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *output.UpgradePlan, s conversion.Scope) error {
out.Components = *(*[]output.ComponentUpgradePlan)(unsafe.Pointer(&in.Components))
out.ConfigVersions = *(*[]output.ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions))
return nil
}
@ -154,6 +191,7 @@ func Convert_v1alpha1_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *ou
func autoConvert_output_UpgradePlan_To_v1alpha1_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error {
out.Components = *(*[]ComponentUpgradePlan)(unsafe.Pointer(&in.Components))
out.ConfigVersions = *(*[]ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions))
return nil
}

View File

@ -50,6 +50,22 @@ func (in *BootstrapToken) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentConfigVersionState) DeepCopyInto(out *ComponentConfigVersionState) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigVersionState.
func (in *ComponentConfigVersionState) DeepCopy() *ComponentConfigVersionState {
if in == nil {
return nil
}
out := new(ComponentConfigVersionState)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
*out = *in
@ -105,6 +121,11 @@ func (in *UpgradePlan) DeepCopyInto(out *UpgradePlan) {
*out = make([]ComponentUpgradePlan, len(*in))
copy(*out, *in)
}
if in.ConfigVersions != nil {
in, out := &in.ConfigVersions, &out.ConfigVersions
*out = make([]ComponentConfigVersionState, len(*in))
copy(*out, *in)
}
return
}

View File

@ -50,6 +50,22 @@ func (in *BootstrapToken) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentConfigVersionState) DeepCopyInto(out *ComponentConfigVersionState) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigVersionState.
func (in *ComponentConfigVersionState) DeepCopy() *ComponentConfigVersionState {
if in == nil {
return nil
}
out := new(ComponentConfigVersionState)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
*out = *in
@ -105,6 +121,11 @@ func (in *UpgradePlan) DeepCopyInto(out *UpgradePlan) {
*out = make([]ComponentUpgradePlan, len(*in))
copy(*out, *in)
}
if in.ConfigVersions != nil {
in, out := &in.ConfigVersions, &out.ConfigVersions
*out = make([]ComponentConfigVersionState, len(*in))
copy(*out, *in)
}
return
}

View File

@ -15,7 +15,7 @@ go_library(
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/apis/output/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/output:go_default_library",
"//cmd/kubeadm/app/cmd/options:go_default_library",
"//cmd/kubeadm/app/cmd/phases/upgrade/node:go_default_library",
"//cmd/kubeadm/app/cmd/phases/workflow:go_default_library",
@ -38,6 +38,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
"//staging/src/k8s.io/client-go/discovery/fake:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/github.com/lithammer/dedent:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",

View File

@ -19,19 +19,25 @@ package upgrade
import (
"fmt"
"io"
"io/ioutil"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/lithammer/dedent"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/version"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
outputapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha1"
outputapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
type planFlags struct {
@ -78,6 +84,13 @@ func runPlan(flags *planFlags, args []string) error {
return errors.Wrap(err, "[upgrade/versions] FATAL")
}
// Fetch the current state of the component configs
klog.V(1).Infoln("[upgrade/plan] analysing component config version states")
configVersionStates, err := getComponentConfigVersionStates(&cfg.ClusterConfiguration, client, flags.cfgPath)
if err != nil {
return errors.WithMessage(err, "[upgrade/versions] FATAL")
}
// No upgrades available
if len(availUpgrades) == 0 {
klog.V(1).Infoln("[upgrade/plan] Awesome, you're up-to-date! Enjoy!")
@ -91,14 +104,23 @@ func runPlan(flags *planFlags, args []string) error {
return err
}
// Actually, this is needed for machine readable output only.
// printUpgradePlan won't output the configVersionStates as it will simply print the same table several times
// in the human readable output if it did so
plan.ConfigVersions = configVersionStates
printUpgradePlan(&up, plan, unstableVersionFlag, isExternalEtcd, os.Stdout)
}
// Finally, print the component config state table
printComponentConfigVersionStates(configVersionStates, os.Stdout)
return nil
}
// newComponentUpgradePlan helper creates outputapiv1alpha1.ComponentUpgradePlan object
func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapiv1alpha1.ComponentUpgradePlan {
return outputapiv1alpha1.ComponentUpgradePlan{
// newComponentUpgradePlan helper creates outputapi.ComponentUpgradePlan object
func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapi.ComponentUpgradePlan {
return outputapi.ComponentUpgradePlan{
Name: name,
CurrentVersion: currentVersion,
NewVersion: newVersion,
@ -107,7 +129,7 @@ func newComponentUpgradePlan(name, currentVersion, newVersion string) outputapiv
// TODO There is currently no way to cleanly output upgrades that involve adding, removing, or changing components
// https://github.com/kubernetes/kubeadm/issues/810 was created to track addressing this.
func appendDNSComponent(components []outputapiv1alpha1.ComponentUpgradePlan, up *upgrade.Upgrade, DNSType kubeadmapi.DNSAddOnType, name string) []outputapiv1alpha1.ComponentUpgradePlan {
func appendDNSComponent(components []outputapi.ComponentUpgradePlan, up *upgrade.Upgrade, DNSType kubeadmapi.DNSAddOnType, name string) []outputapi.ComponentUpgradePlan {
beforeVersion, afterVersion := "", ""
if up.Before.DNSType == DNSType {
beforeVersion = up.Before.DNSVersion
@ -123,7 +145,7 @@ func appendDNSComponent(components []outputapiv1alpha1.ComponentUpgradePlan, up
}
// genUpgradePlan generates output-friendly upgrade plan out of upgrade.Upgrade structure
func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha1.UpgradePlan, string, error) {
func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapi.UpgradePlan, string, error) {
newK8sVersion, err := version.ParseSemantic(up.After.KubeVersion)
if err != nil {
return nil, "", errors.Wrapf(err, "Unable to parse normalized version %q as a semantic version", up.After.KubeVersion)
@ -138,7 +160,7 @@ func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha
}
}
components := []outputapiv1alpha1.ComponentUpgradePlan{}
components := []outputapi.ComponentUpgradePlan{}
if up.CanUpgradeKubelets() {
// The map is of the form <old-version>:<node-count>. Here all the keys are put into a slice and sorted
@ -161,11 +183,29 @@ func genUpgradePlan(up *upgrade.Upgrade, isExternalEtcd bool) (*outputapiv1alpha
components = append(components, newComponentUpgradePlan(constants.Etcd, up.Before.EtcdVersion, up.After.EtcdVersion))
}
return &outputapiv1alpha1.UpgradePlan{Components: components}, unstableVersionFlag, nil
return &outputapi.UpgradePlan{Components: components}, unstableVersionFlag, nil
}
func getComponentConfigVersionStates(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, cfgPath string) ([]outputapi.ComponentConfigVersionState, error) {
docmap := kubeadmapi.DocumentMap{}
if cfgPath != "" {
bytes, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, errors.Wrapf(err, "unable to read config file %q", cfgPath)
}
docmap, err = kubeadmutil.SplitYAMLDocuments(bytes)
if err != nil {
return nil, err
}
}
return componentconfigs.GetVersionStates(cfg, client, docmap)
}
// printUpgradePlan prints a UX-friendly overview of what versions are available to upgrade to
func printUpgradePlan(up *upgrade.Upgrade, plan *outputapiv1alpha1.UpgradePlan, unstableVersionFlag string, isExternalEtcd bool, w io.Writer) {
func printUpgradePlan(up *upgrade.Upgrade, plan *outputapi.UpgradePlan, unstableVersionFlag string, isExternalEtcd bool, w io.Writer) {
// The tab writer writes to the "real" writer w
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
@ -217,8 +257,7 @@ func printUpgradePlan(up *upgrade.Upgrade, plan *outputapiv1alpha1.UpgradePlan,
fmt.Fprintln(w, "")
}
fmt.Fprintln(w, "_____________________________________________________________________")
fmt.Fprintln(w, "")
printLineSeparator(w)
}
// sortedSliceFromStringIntMap returns a slice of the keys in the map sorted alphabetically
@ -230,3 +269,52 @@ func sortedSliceFromStringIntMap(strMap map[string]uint16) []string {
sort.Strings(strSlice)
return strSlice
}
func strOrDash(s string) string {
if s != "" {
return s
}
return "-"
}
func yesOrNo(b bool) string {
if b {
return "yes"
}
return "no"
}
func printLineSeparator(w io.Writer) {
fmt.Fprintln(w, "_____________________________________________________________________")
fmt.Fprintln(w, "")
}
func printComponentConfigVersionStates(versionStates []outputapi.ComponentConfigVersionState, w io.Writer) {
if len(versionStates) == 0 {
fmt.Fprintln(w, "No information available on component configs.")
return
}
fmt.Fprintln(w, dedent.Dedent(`
The table below shows the current state of component configs as understood by this version of kubeadm.
Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or
resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually
upgrade to is denoted in the "PREFERRED VERSION" column.
`))
tabw := tabwriter.NewWriter(w, 10, 4, 3, ' ', 0)
fmt.Fprintln(tabw, "API GROUP\tCURRENT VERSION\tPREFERRED VERSION\tMANUAL UPGRADE REQUIRED")
for _, state := range versionStates {
fmt.Fprintf(tabw,
"%s\t%s\t%s\t%s\n",
state.Group,
strOrDash(state.CurrentVersion),
strOrDash(state.PreferredVersion),
yesOrNo(state.ManualUpgradeRequired),
)
}
tabw.Flush()
printLineSeparator(w)
}

View File

@ -15,6 +15,7 @@ go_library(
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library",
"//cmd/kubeadm/app/apis/output:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
@ -51,6 +52,7 @@ go_test(
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library",
"//cmd/kubeadm/app/apis/output:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",

View File

@ -30,6 +30,7 @@ import (
"k8s.io/klog/v2"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
)
@ -282,6 +283,60 @@ func FetchFromClusterWithLocalOverwrites(clusterCfg *kubeadmapi.ClusterConfigura
return nil
}
// GetVersionStates returns a slice of ComponentConfigVersionState structs
// describing all supported component config groups that were identified on the cluster
func GetVersionStates(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, docmap kubeadmapi.DocumentMap) ([]output.ComponentConfigVersionState, error) {
// We don't want to modify clusterCfg so we make a working deep copy of it.
// Also, we don't want the defaulted component configs so we get rid of them.
scratchClusterCfg := clusterCfg.DeepCopy()
scratchClusterCfg.ComponentConfigs = kubeadmapi.ComponentConfigMap{}
// Call FetchFromClusterWithLocalOverwrites. This will populate the configs it can load and will return all
// UnsupportedConfigVersionError(s) in a sinle instance of a MultipleUnsupportedConfigVersionsError.
var multipleVerErrs UnsupportedConfigVersionsErrorMap
err := FetchFromClusterWithLocalOverwrites(scratchClusterCfg, client, docmap)
if err != nil {
if vererrs, ok := err.(UnsupportedConfigVersionsErrorMap); ok {
multipleVerErrs = vererrs
} else {
// This seems to be a genuine error so we end here
return nil, err
}
}
results := []output.ComponentConfigVersionState{}
for _, handler := range known {
group := handler.GroupVersion.Group
if vererr, ok := multipleVerErrs[group]; ok {
// If there is an UnsupportedConfigVersionError then we are dealing with a case where the config was user
// supplied and requires manual upgrade
results = append(results, output.ComponentConfigVersionState{
Group: group,
CurrentVersion: vererr.OldVersion.Version,
PreferredVersion: vererr.CurrentVersion.Version,
ManualUpgradeRequired: true,
})
} else if _, ok := scratchClusterCfg.ComponentConfigs[group]; ok {
// Normally loaded component config. No manual upgrade required on behalf of users.
results = append(results, output.ComponentConfigVersionState{
Group: group,
CurrentVersion: handler.GroupVersion.Version, // Currently kubeadm supports only one version per API
PreferredVersion: handler.GroupVersion.Version, // group so we can get away with these being the same
})
} else {
// This config was either not present (user did not install an addon) or the config was unsupported kubeadm
// generated one and is therefore skipped so we can automatically re-generate it (no action required on
// behalf of the user).
results = append(results, output.ComponentConfigVersionState{
Group: group,
PreferredVersion: handler.GroupVersion.Version,
})
}
}
return results, nil
}
// Validate is a placeholder for performing a validation on an already loaded component configs in a ClusterConfiguration
// Currently it prints a warning that no validation was performed
func Validate(clusterCfg *kubeadmapi.ClusterConfiguration) field.ErrorList {

View File

@ -17,6 +17,7 @@ limitations under the License.
package componentconfigs
import (
"reflect"
"testing"
"github.com/lithammer/dedent"
@ -28,6 +29,7 @@ import (
clientsetfake "k8s.io/client-go/kubernetes/fake"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
outputapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
@ -246,3 +248,406 @@ func TestFetchFromClusterWithLocalUpgrades(t *testing.T) {
})
}
}
func TestGetVersionStates(t *testing.T) {
tests := []struct {
desc string
objects []runtime.Object
substitutes string
expected []outputapi.ComponentConfigVersionState
}{
{
desc: "Normal config",
objects: []runtime.Object{
kubeproxyConfigMap(`
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
`),
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
},
},
},
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
CurrentVersion: "v1alpha1",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: false,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1beta1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: false,
},
},
},
{
desc: "Normal config ignoring a current substitute",
objects: []runtime.Object{
kubeproxyConfigMap(`
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
`),
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
},
},
},
substitutes: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
`),
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
CurrentVersion: "v1alpha1",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: false,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1beta1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: false,
},
},
},
{
desc: "Normal config with an old substitute",
objects: []runtime.Object{
kubeproxyConfigMap(`
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
`),
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
},
},
},
substitutes: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
CurrentVersion: "v1alpha0",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: true,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1beta1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: false,
},
},
},
{
desc: "Old user supplied config",
objects: []runtime.Object{
kubeproxyConfigMap(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1alpha1
kind: KubeletConfiguration
`),
},
},
},
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
CurrentVersion: "v1alpha0",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: true,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1alpha1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: true,
},
},
},
{
desc: "Old user supplied config with a proper substitute",
objects: []runtime.Object{
kubeproxyConfigMap(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1alpha1
kind: KubeletConfiguration
`),
},
},
},
substitutes: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
`),
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
CurrentVersion: "v1alpha1",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: false,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1alpha1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: true,
},
},
},
{
desc: "Old user supplied config with an old substitute",
objects: []runtime.Object{
kubeproxyConfigMap(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1alpha1
kind: KubeletConfiguration
`),
},
},
},
substitutes: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
CurrentVersion: "v1alpha0",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: true,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1alpha1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: true,
},
},
},
{
desc: "Old kubeadm generated config",
objects: []runtime.Object{
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.KubeProxyConfigMap,
Namespace: metav1.NamespaceSystem,
Annotations: map[string]string{
constants.ComponentConfigHashAnnotationKey: "sha256:8d3dfd7abcac205f6744d8e9db44505cce0c15b0a5395501e272fc18bd54c13c",
},
},
Data: map[string]string{
constants.KubeProxyConfigMapKey: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
},
},
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
},
},
},
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: false,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1beta1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: false,
},
},
},
{
desc: "Old kubeadm generated config with a proper substitute",
objects: []runtime.Object{
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.KubeProxyConfigMap,
Namespace: metav1.NamespaceSystem,
Annotations: map[string]string{
constants.ComponentConfigHashAnnotationKey: "sha256:8d3dfd7abcac205f6744d8e9db44505cce0c15b0a5395501e272fc18bd54c13c",
},
},
Data: map[string]string{
constants.KubeProxyConfigMapKey: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
},
},
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
},
},
},
substitutes: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
`),
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
CurrentVersion: "v1alpha1",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: false,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1beta1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: false,
},
},
},
{
desc: "Old kubeadm generated config with an old substitute",
objects: []runtime.Object{
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.KubeProxyConfigMap,
Namespace: metav1.NamespaceSystem,
Annotations: map[string]string{
constants.ComponentConfigHashAnnotationKey: "sha256:8d3dfd7abcac205f6744d8e9db44505cce0c15b0a5395501e272fc18bd54c13c",
},
},
Data: map[string]string{
constants.KubeProxyConfigMapKey: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
},
},
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.GetKubeletConfigMapName(constants.CurrentKubernetesVersion),
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
},
},
},
substitutes: dedent.Dedent(`
apiVersion: kubeproxy.config.k8s.io/v1alpha0
kind: KubeProxyConfiguration
`),
expected: []outputapi.ComponentConfigVersionState{
{
Group: "kubeproxy.config.k8s.io",
CurrentVersion: "v1alpha0",
PreferredVersion: "v1alpha1",
ManualUpgradeRequired: true,
},
{
Group: "kubelet.config.k8s.io",
CurrentVersion: "v1beta1",
PreferredVersion: "v1beta1",
ManualUpgradeRequired: false,
},
},
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
docmap, err := kubeadmutil.SplitYAMLDocuments([]byte(test.substitutes))
if err != nil {
t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
}
clusterCfg := &kubeadmapi.ClusterConfiguration{
KubernetesVersion: constants.CurrentKubernetesVersion.String(),
}
client := clientsetfake.NewSimpleClientset(test.objects...)
got, err := GetVersionStates(clusterCfg, client, docmap)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(got, test.expected) {
t.Fatalf("unexpected result:\n\texpected: %v\n\tgot: %v", test.expected, got)
}
})
}
}