...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/util/version_test.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/util

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package util
    18  
    19  import (
    20  	"fmt"
    21  	"path"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
    29  )
    30  
    31  func TestEmptyVersion(t *testing.T) {
    32  
    33  	ver, err := KubernetesReleaseVersion("")
    34  	if err == nil {
    35  		t.Error("KubernetesReleaseVersion returned successfully, but error expected")
    36  	}
    37  	if ver != "" {
    38  		t.Error("KubernetesReleaseVersion returned value, expected only error")
    39  	}
    40  }
    41  
    42  func TestValidVersion(t *testing.T) {
    43  	validVersions := []string{
    44  		"v1.3.0",
    45  		"v1.4.0-alpha.0",
    46  		"v1.4.5",
    47  		"v1.4.0-beta.0",
    48  		"v2.0.0",
    49  		"v1.6.0-alpha.0.536+d60d9f3269288f",
    50  		"v1.5.0-alpha.0.1078+1044b6822497da-pull",
    51  		"v1.5.0-alpha.1.822+49b9e32fad9f32-pull-gke-gci",
    52  		"v1.6.1+coreos.0",
    53  		"1.7.1",
    54  	}
    55  	for _, s := range validVersions {
    56  		t.Run(s, func(t *testing.T) {
    57  			ver, err := kubernetesReleaseVersion(s, errorFetcher)
    58  			t.Log("Valid: ", s, ver, err)
    59  			if err != nil {
    60  				t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
    61  			}
    62  			if ver != s && ver != "v"+s {
    63  				t.Errorf("KubernetesReleaseVersion should return same valid version string. %q != %q", s, ver)
    64  			}
    65  		})
    66  	}
    67  }
    68  
    69  func TestInvalidVersion(t *testing.T) {
    70  	invalidVersions := []string{
    71  		"v1.3",
    72  		"1.4",
    73  		"b1.4.0",
    74  		"c1.4.5+git",
    75  		"something1.2",
    76  	}
    77  	for _, s := range invalidVersions {
    78  		t.Run(s, func(t *testing.T) {
    79  			ver, err := kubernetesReleaseVersion(s, errorFetcher)
    80  			t.Log("Invalid: ", s, ver, err)
    81  			if err == nil {
    82  				t.Errorf("KubernetesReleaseVersion error expected for version %q, but returned successfully", s)
    83  			}
    84  			if ver != "" {
    85  				t.Errorf("KubernetesReleaseVersion should return empty string in case of error. Returned %q for version %q", ver, s)
    86  			}
    87  		})
    88  	}
    89  }
    90  
    91  func TestValidConvenientForUserVersion(t *testing.T) {
    92  	validVersions := []string{
    93  		"1.4.0",
    94  		"1.4.5+git",
    95  		"1.6.1_coreos.0",
    96  	}
    97  	for _, s := range validVersions {
    98  		t.Run(s, func(t *testing.T) {
    99  			ver, err := kubernetesReleaseVersion(s, errorFetcher)
   100  			t.Log("Valid: ", s, ver, err)
   101  			if err != nil {
   102  				t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
   103  			}
   104  			if ver != "v"+s {
   105  				t.Errorf("KubernetesReleaseVersion should return semantic version string. %q vs. %q", s, ver)
   106  			}
   107  		})
   108  	}
   109  }
   110  
   111  func TestVersionFromNetwork(t *testing.T) {
   112  	type T struct {
   113  		Content              string
   114  		Expected             string
   115  		FetcherErrorExpected bool
   116  		ErrorExpected        bool
   117  	}
   118  
   119  	currentVersion := normalizedBuildVersion(constants.CurrentKubernetesVersion.String())
   120  
   121  	cases := map[string]T{
   122  		"stable":          {"stable-1", "v1.4.6", false, false}, // recursive pointer to stable-1
   123  		"stable-1":        {"v1.4.6", "v1.4.6", false, false},
   124  		"stable-1.3":      {"v1.3.10", "v1.3.10", false, false},
   125  		"latest":          {"v1.6.0-alpha.0", "v1.6.0-alpha.0", false, false},
   126  		"latest-1.3":      {"v1.3.11-beta.0", "v1.3.11-beta.0", false, false},
   127  		"latest-1.5":      {"", currentVersion, true, false}, // fallback to currentVersion on fetcher error
   128  		"invalid-version": {"", "", false, true},             // invalid version cannot be parsed
   129  	}
   130  
   131  	for k, v := range cases {
   132  		t.Run(k, func(t *testing.T) {
   133  
   134  			fileFetcher := func(url string, timeout time.Duration) (string, error) {
   135  				key := strings.TrimSuffix(path.Base(url), ".txt")
   136  				res, found := cases[key]
   137  				if found {
   138  					if v.FetcherErrorExpected {
   139  						return "error", errors.New("expected error")
   140  					}
   141  					return res.Content, nil
   142  				}
   143  				return "Unknown test case key!", errors.New("unknown test case key")
   144  			}
   145  
   146  			ver, err := kubernetesReleaseVersion(k, fileFetcher)
   147  			t.Logf("Key: %q. Result: %q, Error: %v", k, ver, err)
   148  			switch {
   149  			case err != nil && !v.ErrorExpected:
   150  				t.Errorf("KubernetesReleaseVersion: unexpected error for %q. Error: %+v", k, err)
   151  			case err == nil && v.ErrorExpected:
   152  				t.Errorf("KubernetesReleaseVersion: error expected for key %q, but result is %q", k, ver)
   153  			case ver != v.Expected:
   154  				t.Errorf("KubernetesReleaseVersion: unexpected result for key %q. Expected: %q Actual: %q", k, v.Expected, ver)
   155  			}
   156  		})
   157  	}
   158  }
   159  
   160  func TestVersionToTag(t *testing.T) {
   161  	type T struct {
   162  		input    string
   163  		expected string
   164  	}
   165  	cases := []T{
   166  		// NOP
   167  		{"", ""},
   168  		// Official releases
   169  		{"v1.0.0", "v1.0.0"},
   170  		// CI or custom builds
   171  		{"v10.1.2-alpha.1.100+0123456789abcdef+SOMETHING", "v10.1.2-alpha.1.100_0123456789abcdef_SOMETHING"},
   172  		// random and invalid input: should return safe value
   173  		{"v1,0!0+üñµ", "v1_0_0____"},
   174  	}
   175  
   176  	for _, tc := range cases {
   177  		t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
   178  			tag := KubernetesVersionToImageTag(tc.input)
   179  			t.Logf("KubernetesVersionToImageTag: Input: %q. Result: %q. Expected: %q", tc.input, tag, tc.expected)
   180  			if tag != tc.expected {
   181  				t.Errorf("failed KubernetesVersionToImageTag: Input: %q. Result: %q. Expected: %q", tc.input, tag, tc.expected)
   182  			}
   183  		})
   184  	}
   185  }
   186  
   187  func TestSplitVersion(t *testing.T) {
   188  	type T struct {
   189  		input  string
   190  		bucket string
   191  		label  string
   192  		valid  bool
   193  	}
   194  	cases := []T{
   195  		// Release area
   196  		{"v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
   197  		{"v1.8.0-alpha.2.1231+afabd012389d53a", "https://dl.k8s.io/release", "v1.8.0-alpha.2.1231+afabd012389d53a", true},
   198  		{"release/v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
   199  		{"release/latest-1.7", "https://dl.k8s.io/release", "latest-1.7", true},
   200  		// CI builds area
   201  		{"ci/latest", "https://storage.googleapis.com/k8s-release-dev/ci", "latest", true},
   202  		{"ci/latest-1.7", "https://storage.googleapis.com/k8s-release-dev/ci", "latest-1.7", true},
   203  		// unknown label in default (release) area: splitVersion validate only areas.
   204  		{"unknown-1", "https://dl.k8s.io/release", "unknown-1", true},
   205  		// unknown area, not valid input.
   206  		{"unknown/latest-1", "", "", false},
   207  		// invalid input
   208  		{"", "", "", false},
   209  		{"ci/", "", "", false},
   210  	}
   211  
   212  	for _, tc := range cases {
   213  		t.Run(fmt.Sprintf("input:%s/label:%s", tc.input, tc.label), func(t *testing.T) {
   214  			bucket, label, err := splitVersion(tc.input)
   215  			switch {
   216  			case err != nil && tc.valid:
   217  				t.Errorf("splitVersion: unexpected error for %q. Error: %v", tc.input, err)
   218  			case err == nil && !tc.valid:
   219  				t.Errorf("splitVersion: error expected for key %q, but result is %q, %q", tc.input, bucket, label)
   220  			case bucket != tc.bucket:
   221  				t.Errorf("splitVersion: unexpected bucket result for key %q. Expected: %q Actual: %q", tc.input, tc.bucket, bucket)
   222  			case label != tc.label:
   223  				t.Errorf("splitVersion: unexpected label result for key %q. Expected: %q Actual: %q", tc.input, tc.label, label)
   224  			}
   225  		})
   226  	}
   227  }
   228  
   229  func TestKubernetesIsCIVersion(t *testing.T) {
   230  	type T struct {
   231  		input    string
   232  		expected bool
   233  	}
   234  	cases := []T{
   235  		{"", false},
   236  		// Official releases
   237  		{"v1.0.0", false},
   238  		{"release/v1.0.0", false},
   239  		// CI builds
   240  		{"ci/latest-1", true},
   241  		{"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
   242  		{"ci/", false},
   243  	}
   244  
   245  	for _, tc := range cases {
   246  		t.Run(fmt.Sprintf("input:%s/expected:%t", tc.input, tc.expected), func(t *testing.T) {
   247  			result := KubernetesIsCIVersion(tc.input)
   248  			t.Logf("KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
   249  			if result != tc.expected {
   250  				t.Errorf("failed KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
   251  			}
   252  		})
   253  	}
   254  }
   255  
   256  // Validate KubernetesReleaseVersion but with bucket prefixes
   257  func TestCIBuildVersion(t *testing.T) {
   258  	type T struct {
   259  		input    string
   260  		expected string
   261  		valid    bool
   262  	}
   263  	cases := []T{
   264  		// Official releases
   265  		{"v1.7.0", "v1.7.0", true},
   266  		{"release/v1.8.0", "v1.8.0", true},
   267  		{"1.4.0-beta.0", "v1.4.0-beta.0", true},
   268  		{"release/0invalid", "", false},
   269  		// CI or custom builds
   270  		{"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
   271  		{"ci/1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
   272  		{"ci/0invalid", "", false},
   273  		{"0invalid", "", false},
   274  	}
   275  
   276  	for _, tc := range cases {
   277  		t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
   278  
   279  			fileFetcher := func(url string, timeout time.Duration) (string, error) {
   280  				if tc.valid {
   281  					return tc.expected, nil
   282  				}
   283  				return "Unknown test case key!", errors.New("unknown test case key")
   284  			}
   285  
   286  			ver, err := kubernetesReleaseVersion(tc.input, fileFetcher)
   287  			t.Logf("Input: %q. Result: %q, Error: %v", tc.input, ver, err)
   288  			switch {
   289  			case err != nil && tc.valid:
   290  				t.Errorf("KubernetesReleaseVersion: unexpected error for input %q. Error: %v", tc.input, err)
   291  			case err == nil && !tc.valid:
   292  				t.Errorf("KubernetesReleaseVersion: error expected for input %q, but result is %q", tc.input, ver)
   293  			case ver != tc.expected:
   294  				t.Errorf("KubernetesReleaseVersion: unexpected result for input %q. Expected: %q Actual: %q", tc.input, tc.expected, ver)
   295  			}
   296  		})
   297  	}
   298  }
   299  
   300  func TestNormalizedBuildVersionVersion(t *testing.T) {
   301  	type T struct {
   302  		input    string
   303  		expected string
   304  	}
   305  	cases := []T{
   306  		{"v1.7.0", "v1.7.0"},
   307  		{"v1.8.0-alpha.2.1231+afabd012389d53a", "v1.8.0-alpha.2.1231+afabd012389d53a"},
   308  		{"1.7.0", "v1.7.0"},
   309  		{"unknown-1", ""},
   310  	}
   311  
   312  	for _, tc := range cases {
   313  		t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
   314  			output := normalizedBuildVersion(tc.input)
   315  			if output != tc.expected {
   316  				t.Errorf("normalizedBuildVersion: unexpected output %q for input %q. Expected: %q", output, tc.input, tc.expected)
   317  			}
   318  		})
   319  	}
   320  }
   321  
   322  func TestKubeadmVersion(t *testing.T) {
   323  	type T struct {
   324  		name         string
   325  		input        string
   326  		output       string
   327  		outputError  bool
   328  		parsingError bool
   329  	}
   330  	cases := []T{
   331  		{
   332  			name:   "valid version with label and metadata",
   333  			input:  "v1.8.0-alpha.2.1231+afabd012389d53a",
   334  			output: "v1.8.0-alpha.2",
   335  		},
   336  		{
   337  			name:   "valid version with label and extra metadata",
   338  			input:  "v1.8.0-alpha.2.1231+afabd012389d53a.extra",
   339  			output: "v1.8.0-alpha.2",
   340  		},
   341  		{
   342  			name:   "valid patch version with label and extra metadata",
   343  			input:  "v1.11.3-beta.0.38+135cc4c1f47994",
   344  			output: "v1.11.2",
   345  		},
   346  		{
   347  			name:   "valid version with label extra",
   348  			input:  "v1.8.0-alpha.2.1231",
   349  			output: "v1.8.0-alpha.2",
   350  		},
   351  		{
   352  			name:   "valid patch version with label",
   353  			input:  "v1.9.11-beta.0",
   354  			output: "v1.9.10",
   355  		},
   356  		{
   357  			name:   "handle version with partial label",
   358  			input:  "v1.8.0-alpha",
   359  			output: "v1.8.0-alpha.0",
   360  		},
   361  		{
   362  			name:   "handle version missing 'v'",
   363  			input:  "1.11.0",
   364  			output: "v1.11.0",
   365  		},
   366  		{
   367  			name:   "valid version without label and metadata",
   368  			input:  "v1.8.0",
   369  			output: "v1.8.0",
   370  		},
   371  		{
   372  			name:   "valid patch version without label and metadata",
   373  			input:  "v1.8.2",
   374  			output: "v1.8.2",
   375  		},
   376  		{
   377  			name:         "invalid version",
   378  			input:        "foo",
   379  			parsingError: true,
   380  		},
   381  		{
   382  			name:         "invalid version with stray dash",
   383  			input:        "v1.9.11-",
   384  			parsingError: true,
   385  		},
   386  		{
   387  			name:         "invalid version without patch release",
   388  			input:        "v1.9",
   389  			parsingError: true,
   390  		},
   391  		{
   392  			name:         "invalid version with label and stray dot",
   393  			input:        "v1.8.0-alpha.2.",
   394  			parsingError: true,
   395  		},
   396  		{
   397  			name:        "invalid version with label and metadata",
   398  			input:       "v1.8.0-alpha.2.1231+afabd012389d53a",
   399  			output:      "v1.8.0-alpha.3",
   400  			outputError: true,
   401  		},
   402  	}
   403  
   404  	for _, tc := range cases {
   405  		t.Run(tc.name, func(t *testing.T) {
   406  			output, err := kubeadmVersion(tc.input)
   407  			if (err != nil) != tc.parsingError {
   408  				t.Fatalf("expected error: %v, got: %v", tc.parsingError, err != nil)
   409  			}
   410  			if (output != tc.output) != tc.outputError {
   411  				t.Fatalf("expected output: %s, got: %s, for input: %s", tc.output, output, tc.input)
   412  			}
   413  		})
   414  	}
   415  }
   416  
   417  func TestValidateStableVersion(t *testing.T) {
   418  	type T struct {
   419  		name          string
   420  		remoteVersion string
   421  		clientVersion string
   422  		output        string
   423  		expectedError bool
   424  	}
   425  	cases := []T{
   426  		{
   427  			name:          "valid: remote version is newer; return stable label [1]",
   428  			remoteVersion: "v1.12.0",
   429  			clientVersion: "v1.11.0",
   430  			output:        "stable-1.11",
   431  		},
   432  		{
   433  			name:          "valid: remote version is newer; return stable label [2]",
   434  			remoteVersion: "v2.0.0",
   435  			clientVersion: "v1.11.0",
   436  			output:        "stable-1.11",
   437  		},
   438  		{
   439  			name:          "valid: remote version is newer; return stable label [3]",
   440  			remoteVersion: "v2.1.5",
   441  			clientVersion: "v1.11.5",
   442  			output:        "stable-1.11",
   443  		},
   444  		{
   445  			name:          "valid: return the remote version as it is part of the same release",
   446  			remoteVersion: "v1.11.5",
   447  			clientVersion: "v1.11.0",
   448  			output:        "v1.11.5",
   449  		},
   450  		{
   451  			name:          "valid: return the same version",
   452  			remoteVersion: "v1.11.0",
   453  			clientVersion: "v1.11.0",
   454  			output:        "v1.11.0",
   455  		},
   456  		{
   457  			name:          "invalid: client version is empty",
   458  			remoteVersion: "v1.12.1",
   459  			clientVersion: "",
   460  			expectedError: true,
   461  		},
   462  		{
   463  			name:          "invalid: error parsing the remote version",
   464  			remoteVersion: "invalid-version",
   465  			clientVersion: "v1.12.0",
   466  			expectedError: true,
   467  		},
   468  		{
   469  			name:          "invalid: error parsing the client version",
   470  			remoteVersion: "v1.12.0",
   471  			clientVersion: "invalid-version",
   472  			expectedError: true,
   473  		},
   474  	}
   475  
   476  	for _, tc := range cases {
   477  		t.Run(tc.name, func(t *testing.T) {
   478  			output, err := validateStableVersion(tc.remoteVersion, tc.clientVersion)
   479  			if (err != nil) != tc.expectedError {
   480  				t.Fatalf("expected error: %v, got: %v", tc.expectedError, err != nil)
   481  			}
   482  			if output != tc.output {
   483  				t.Fatalf("expected output: %s, got: %s", tc.output, output)
   484  			}
   485  		})
   486  	}
   487  }
   488  
   489  func errorFetcher(url string, timeout time.Duration) (string, error) {
   490  	return "should not make internet calls", errors.Errorf("should not make internet calls, tried to request url: %s", url)
   491  }
   492  

View as plain text