Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kubeadm: handle stable-1 as the default version #69301

Merged
merged 1 commit into from
Oct 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 39 additions & 5 deletions cmd/kubeadm/app/util/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func KubernetesReleaseVersion(version string) (string, error) {

// kubeReleaseLabelRegex matches labels such as: latest, latest-1, latest-1.10
if kubeReleaseLabelRegex.MatchString(versionLabel) {
var clientVersion string
// Try to obtain a client version.
clientVersion, _ = kubeadmVersion(pkgversion.Get().String())
// Fetch version from the internet.
url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
body, err := fetchFromURL(url, getReleaseVersionTimeout)
if err != nil {
Expand All @@ -87,11 +91,13 @@ func KubernetesReleaseVersion(version string) (string, error) {
}
// Handle air-gapped environments by falling back to the client version.
glog.Infof("could not fetch a Kubernetes version from the internet: %v", err)
body, err = kubeadmVersion(pkgversion.Get().String())
if err != nil {
return "", err
}
glog.Infof("falling back to the local client version: %s", body)
glog.Infof("falling back to the local client version: %s", clientVersion)
return KubernetesReleaseVersion(clientVersion)
}
// both the client and the remote version are obtained; validate them and pick a stable version
body, err = validateStableVersion(body, clientVersion)
if err != nil {
return "", err
}
// Re-validate received version and return.
return KubernetesReleaseVersion(body)
Expand Down Expand Up @@ -204,3 +210,31 @@ func kubeadmVersion(info string) (string, error) {
vStr := fmt.Sprintf("v%d.%d.%d%s", v.Major(), v.Minor(), patch, pre)
return vStr, nil
}

// Validate if the remote version is one Minor release newer than the client version.
// This is done to conform with "stable-X" and only allow remote versions from
// the same Patch level release.
func validateStableVersion(remoteVersion, clientVersion string) (string, error) {
if clientVersion == "" {
glog.Infof("could not obtain client version; using remote version: %s", remoteVersion)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glog should be used with verbosity level, isn't it ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless we want to always show the warning with file and line, etc, no?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see that as a critical warning, but ok.

return remoteVersion, nil
}

verRemote, err := versionutil.ParseGeneric(remoteVersion)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, here we have content of remote file. right now, it is supposed to be exact version. but the idea of recursive validation was that if label 'stable-1' will be resolved to another label, e.g. 'stable-1.12', this function will try to resolve one more remote label. If this function will expect that remote content is always parsable version, it will break previous functionality.

Copy link
Member Author

@neolit123 neolit123 Oct 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the review but this was in flight for 10 days.
also just created the cherry pick for 1.12.

i see what you are saying but in my tests i wasn't able to reach such a breakage.
we do fall-back to stable-1.12, if stable-1 is much newer, recurse and it works fine.

now, if stable-1 returns a non-semantic version as contents, that's really a breakage with the endpoint from my perspective.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for delay with commenting on that, somehow screwed up notifications on this PR :(

Yes, that scenario is not covered by tests. It was previously implicitly working with nesting of the labels. Support for nested label resolution is not critical, but nice to have. I can do separate PR for that later on.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good! thanks.

if err != nil {
return "", fmt.Errorf("remote version error: %v", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grumble ... errors.Wrap

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure.
i don't like that it's not part of golang yet, but we can possibly SED them all at once.

}
verClient, err := versionutil.ParseGeneric(clientVersion)
if err != nil {
return "", fmt.Errorf("client version error: %v", err)
}
// If the remote Major version is bigger or if the Major versions are the same,
// but the remote Minor is bigger use the client version release. This handles Major bumps too.
if verClient.Major() < verRemote.Major() ||
(verClient.Major() == verRemote.Major()) && verClient.Minor() < verRemote.Minor() {
estimatedRelease := fmt.Sprintf("stable-%d.%d", verClient.Major(), verClient.Minor())
glog.Infof("remote version is much newer: %s; falling back to: %s", remoteVersion, estimatedRelease)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This almost seems like a bug.
If a person has 1.11.1 download, they should be able to get the latest 1.11.y and it should just work, if we fallback that's almost unexpected behavior.

imo we need to switch back to stable-1.X in the 1.12 series bits.

Copy link
Member Author

@neolit123 neolit123 Oct 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a person has 1.11.1 download, they should be able to get the latest 1.11.y

the above code should do just that.

for 1.11.1 it falls back to stable-1.11, which can be e.g. 1.11.5.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I see that now. K, I'm good.

return estimatedRelease, nil
}
return remoteVersion, nil
}
72 changes: 72 additions & 0 deletions cmd/kubeadm/app/util/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,75 @@ func TestKubeadmVersion(t *testing.T) {
})
}
}

func TestValidateStableVersion(t *testing.T) {
type T struct {
name string
remoteVersion string
clientVersion string
output string
expectedError bool
}
cases := []T{
{
name: "valid: remote version is newer; return stable label [1]",
remoteVersion: "v1.12.0",
clientVersion: "v1.11.0",
output: "stable-1.11",
},
{
name: "valid: remote version is newer; return stable label [2]",
remoteVersion: "v2.0.0",
clientVersion: "v1.11.0",
output: "stable-1.11",
},
{
name: "valid: remote version is newer; return stable label [3]",
remoteVersion: "v2.1.5",
clientVersion: "v1.11.5",
output: "stable-1.11",
},
{
name: "valid: return the remote version as it is part of the same release",
remoteVersion: "v1.11.5",
clientVersion: "v1.11.0",
output: "v1.11.5",
},
{
name: "valid: return the same version",
remoteVersion: "v1.11.0",
clientVersion: "v1.11.0",
output: "v1.11.0",
},
{
name: "valid: client version is empty; use remote version",
remoteVersion: "v1.12.1",
clientVersion: "",
output: "v1.12.1",
},
{
name: "invalid: error parsing the remote version",
remoteVersion: "invalid-version",
clientVersion: "v1.12.0",
expectedError: true,
},
{
name: "invalid: error parsing the client version",
remoteVersion: "v1.12.0",
clientVersion: "invalid-version",
expectedError: true,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
output, err := validateStableVersion(tc.remoteVersion, tc.clientVersion)
if (err != nil) != tc.expectedError {
t.Fatalf("expected error: %v, got: %v", tc.expectedError, err != nil)
}
if output != tc.output {
t.Fatalf("expected output: %s, got: %s", tc.output, output)
}
})
}
}