diff --git a/cmd/kubeadm/app/preflight/BUILD b/cmd/kubeadm/app/preflight/BUILD index 28212bc28e..b2ae8681ae 100644 --- a/cmd/kubeadm/app/preflight/BUILD +++ b/cmd/kubeadm/app/preflight/BUILD @@ -17,6 +17,8 @@ go_library( "//pkg/api/validation:go_default_library", "//pkg/kubeapiserver/authorizer/modes:go_default_library", "//pkg/util/initsystem:go_default_library", + "//pkg/util/version:go_default_library", + "//pkg/version:go_default_library", "//plugin/cmd/kube-scheduler/app/options:go_default_library", "//test/e2e_node/system:go_default_library", "//vendor/github.com/PuerkitoBio/purell:go_default_library", diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index 9d2ddee7e3..1e766db4db 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -28,6 +28,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "time" "crypto/tls" @@ -46,6 +47,8 @@ import ( "k8s.io/kubernetes/pkg/api/validation" authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" "k8s.io/kubernetes/pkg/util/initsystem" + versionutil "k8s.io/kubernetes/pkg/util/version" + kubeadmversion "k8s.io/kubernetes/pkg/version" schoptions "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/options" "k8s.io/kubernetes/test/e2e_node/system" ) @@ -394,6 +397,40 @@ func (sysver SystemVerificationCheck) Check() (warnings, errors []error) { return warns, nil } +type KubernetesVersionCheck struct { + KubeadmVersion string + KubernetesVersion string +} + +func (kubever KubernetesVersionCheck) Check() (warnings, errors []error) { + + // Skip this check for "super-custom builds", where apimachinery/the overall codebase version is not set. + if strings.HasPrefix(kubever.KubeadmVersion, "v0.0.0") { + return nil, nil + } + + kadmVersion, err := versionutil.ParseSemantic(kubever.KubeadmVersion) + if err != nil { + return nil, []error{fmt.Errorf("couldn't parse kubeadm version %q: %v", kubever.KubeadmVersion, err)} + } + + k8sVersion, err := versionutil.ParseSemantic(kubever.KubernetesVersion) + if err != nil { + return nil, []error{fmt.Errorf("couldn't parse kubernetes version %q: %v", kubever.KubernetesVersion, err)} + } + + // Checks if k8sVersion greater or equal than the first unsupported versions by current version of kubeadm, + // that is major.minor+1 (all patch and pre-releases versions included) + // NB. in semver patches number is a numeric, while prerelease is a string where numeric identifiers always have lower precedence than non-numeric identifiers. + // thus setting the value to x.y.0-0 we are defining the very first patch - prereleases within x.y minor release. + firstUnsupportedVersion := versionutil.MustParseSemantic(fmt.Sprintf("%d.%d.%s", kadmVersion.Major(), kadmVersion.Minor()+1, "0-0")) + if k8sVersion.AtLeast(firstUnsupportedVersion) { + return []error{fmt.Errorf("kubernetes version is greater than kubeadm version. Please consider to upgrade kubeadm. kubernetes version: %s. Kubeadm version: %d.%d.x", k8sVersion, kadmVersion.Components()[0], kadmVersion.Components()[1])}, nil + } + + return nil, nil +} + type etcdVersionResponse struct { Etcdserver string `json:"etcdserver"` Etcdcluster string `json:"etcdcluster"` @@ -534,6 +571,7 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error { } checks := []Checker{ + KubernetesVersionCheck{KubernetesVersion: cfg.KubernetesVersion, KubeadmVersion: kubeadmversion.Get().GitVersion}, SystemVerificationCheck{}, IsRootCheck{}, HostnameCheck{nodeName: cfg.NodeName}, diff --git a/cmd/kubeadm/app/preflight/checks_test.go b/cmd/kubeadm/app/preflight/checks_test.go index f790f35429..2db21a775f 100644 --- a/cmd/kubeadm/app/preflight/checks_test.go +++ b/cmd/kubeadm/app/preflight/checks_test.go @@ -26,7 +26,6 @@ import ( "os" - "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) @@ -180,7 +179,7 @@ func TestRunInitMasterChecks(t *testing.T) { }{ { cfg: &kubeadmapi.MasterConfiguration{ - API: kubeadm.API{AdvertiseAddress: "foo"}, + API: kubeadmapi.API{AdvertiseAddress: "foo"}, }, expected: false, }, @@ -347,3 +346,66 @@ func TestConfigCertAndKey(t *testing.T) { ) } } + +func TestKubernetesVersionCheck(t *testing.T) { + var tests = []struct { + check KubernetesVersionCheck + expectWarnings bool + }{ + { + check: KubernetesVersionCheck{ + KubeadmVersion: "v1.6.6", //Same version + KubernetesVersion: "v1.6.6", + }, + expectWarnings: false, + }, + { + check: KubernetesVersionCheck{ + KubeadmVersion: "v1.6.6", //KubernetesVersion version older than KubeadmVersion + KubernetesVersion: "v1.5.5", + }, + expectWarnings: false, + }, + { + check: KubernetesVersionCheck{ + KubeadmVersion: "v1.6.6", //KubernetesVersion newer than KubeadmVersion, within the same minor release (new patch) + KubernetesVersion: "v1.6.7", + }, + expectWarnings: false, + }, + { + check: KubernetesVersionCheck{ + KubeadmVersion: "v1.6.6", //KubernetesVersion newer than KubeadmVersion, in a different minor/in pre-release + KubernetesVersion: "v1.7.0-alpha.0", + }, + expectWarnings: true, + }, + { + check: KubernetesVersionCheck{ + KubeadmVersion: "v1.6.6", //KubernetesVersion newer than KubeadmVersion, in a different minor/stable + KubernetesVersion: "v1.7.0", + }, + expectWarnings: true, + }, + { + check: KubernetesVersionCheck{ + KubeadmVersion: "v0.0.0", //"super-custom" builds - Skip this check + KubernetesVersion: "v1.7.0", + }, + expectWarnings: false, + }, + } + + for _, rt := range tests { + warning, _ := rt.check.Check() + if (warning != nil) != rt.expectWarnings { + t.Errorf( + "failed KubernetesVersionCheck:\n\texpected: %t\n\t actual: %t (KubeadmVersion:%s, KubernetesVersion: %s)", + rt.expectWarnings, + (warning != nil), + rt.check.KubeadmVersion, + rt.check.KubernetesVersion, + ) + } + } +} diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go index 327f2e67f4..e8cd0cecfa 100644 --- a/pkg/util/version/version.go +++ b/pkg/util/version/version.go @@ -121,11 +121,39 @@ func MustParseSemantic(str string) *Version { return v } +// Major returns the major release number +func (v *Version) Major() uint { + return v.components[0] +} + +// Minor returns the minor release number +func (v *Version) Minor() uint { + return v.components[1] +} + +// Patch returns the patch release number if v is a Semantic Version, or 0 +func (v *Version) Patch() uint { + if len(v.components) < 3 { + return 0 + } + return v.components[2] +} + // BuildMetadata returns the build metadata, if v is a Semantic Version, or "" func (v *Version) BuildMetadata() string { return v.buildMetadata } +// PreRelease returns the prerelease metadata, if v is a Semantic Version, or "" +func (v *Version) PreRelease() string { + return v.preRelease +} + +// Components returns the version number components +func (v *Version) Components() []uint { + return v.components +} + // String converts a Version back to a string; note that for versions parsed with // ParseGeneric, this will not include the trailing uninterpreted portion of the version // number. diff --git a/pkg/util/version/version_test.go b/pkg/util/version/version_test.go index 555c59b4b7..eb231b1e2b 100644 --- a/pkg/util/version/version_test.go +++ b/pkg/util/version/version_test.go @@ -18,6 +18,7 @@ package version import ( "fmt" + "reflect" "testing" ) @@ -257,3 +258,75 @@ func TestBadGenericVersions(t *testing.T) { } } } + +func TestComponents(t *testing.T) { + + var tests = []struct { + version string + semver bool + expectedComponents []uint + expectedMajor uint + expectedMinor uint + expectedPatch uint + expectedPreRelease string + expectedBuildMetadata string + }{ + { + version: "1.0.2", + semver: true, + expectedComponents: []uint{1, 0, 2}, + expectedMajor: 1, + expectedMinor: 0, + expectedPatch: 2, + }, + { + version: "1.0.2-alpha+001", + semver: true, + expectedComponents: []uint{1, 0, 2}, + expectedMajor: 1, + expectedMinor: 0, + expectedPatch: 2, + expectedPreRelease: "alpha", + expectedBuildMetadata: "001", + }, + { + version: "1.2", + semver: false, + expectedComponents: []uint{1, 2}, + expectedMajor: 1, + expectedMinor: 2, + }, + { + version: "1.0.2-beta+exp.sha.5114f85", + semver: true, + expectedComponents: []uint{1, 0, 2}, + expectedMajor: 1, + expectedMinor: 0, + expectedPatch: 2, + expectedPreRelease: "beta", + expectedBuildMetadata: "exp.sha.5114f85", + }, + } + + for _, test := range tests { + version, _ := parse(test.version, test.semver) + if !reflect.DeepEqual(test.expectedComponents, version.Components()) { + t.Error("parse returned un'expected components") + } + if test.expectedMajor != version.Major() { + t.Errorf("parse returned version.Major %d, expected %d", test.expectedMajor, version.Major()) + } + if test.expectedMinor != version.Minor() { + t.Errorf("parse returned version.Minor %d, expected %d", test.expectedMinor, version.Minor()) + } + if test.expectedPatch != version.Patch() { + t.Errorf("parse returned version.Patch %d, expected %d", test.expectedPatch, version.Patch()) + } + if test.expectedPreRelease != version.PreRelease() { + t.Errorf("parse returned version.PreRelease %s, expected %s", test.expectedPreRelease, version.PreRelease()) + } + if test.expectedBuildMetadata != version.BuildMetadata() { + t.Errorf("parse returned version.BuildMetadata %s, expected %s", test.expectedBuildMetadata, version.BuildMetadata()) + } + } +}