sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/cluster/upgrader_info_test.go (about)

     1  /*
     2  Copyright 2020 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 cluster
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/version"
    26  
    27  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    28  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    29  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
    30  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
    31  	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
    32  )
    33  
    34  func Test_providerUpgrader_getUpgradeInfo(t *testing.T) {
    35  	type fields struct {
    36  		reader config.Reader
    37  		repo   repository.Repository
    38  	}
    39  	type args struct {
    40  		provider clusterctlv1.Provider
    41  	}
    42  	tests := []struct {
    43  		name    string
    44  		fields  fields
    45  		args    args
    46  		want    *upgradeInfo
    47  		wantErr bool
    48  	}{
    49  		{
    50  			name: "pass when current and next version are current contract",
    51  			fields: fields{
    52  				reader: test.NewFakeReader().
    53  					WithProvider("p1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"),
    54  				repo: repository.NewMemoryRepository().
    55  					WithVersions("v1.0.0", "v1.0.1", "v1.0.2", "v1.1.0").
    56  					WithMetadata("v1.1.0", &clusterctlv1.Metadata{
    57  						ReleaseSeries: []clusterctlv1.ReleaseSeries{
    58  							{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
    59  							{Major: 1, Minor: 1, Contract: test.CurrentCAPIContract},
    60  						},
    61  					}),
    62  			},
    63  			args: args{
    64  				provider: fakeProvider("p1", clusterctlv1.InfrastructureProviderType, "v1.0.1", "p1-system"),
    65  			},
    66  			want: &upgradeInfo{
    67  				metadata: &clusterctlv1.Metadata{
    68  					TypeMeta: metav1.TypeMeta{
    69  						APIVersion: clusterctlv1.GroupVersion.String(),
    70  						Kind:       "Metadata",
    71  					},
    72  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
    73  						{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
    74  						{Major: 1, Minor: 1, Contract: test.CurrentCAPIContract},
    75  					},
    76  				},
    77  				currentVersion:  version.MustParseSemantic("v1.0.1"),
    78  				currentContract: test.CurrentCAPIContract,
    79  				nextVersions: []version.Version{
    80  					// v1.0.1 (the current version) and older are ignored
    81  					*version.MustParseSemantic("v1.0.2"),
    82  					*version.MustParseSemantic("v1.1.0"),
    83  				},
    84  			},
    85  			wantErr: false,
    86  		},
    87  		{
    88  			name: "pass when current version is in previous contract (Not supported), next version in current contract", // upgrade plan should report unsupported options
    89  			fields: fields{
    90  				reader: test.NewFakeReader().
    91  					WithProvider("p1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"),
    92  				repo: repository.NewMemoryRepository().
    93  					WithVersions("v1.0.0", "v1.0.1", "v1.0.2", "v1.1.0").
    94  					WithMetadata("v1.1.0", &clusterctlv1.Metadata{
    95  						ReleaseSeries: []clusterctlv1.ReleaseSeries{
    96  							{Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported},
    97  							{Major: 1, Minor: 1, Contract: test.CurrentCAPIContract},
    98  						},
    99  					}),
   100  			},
   101  			args: args{
   102  				provider: fakeProvider("p1", clusterctlv1.InfrastructureProviderType, "v1.0.1", "p1-system"),
   103  			},
   104  			want: &upgradeInfo{
   105  				metadata: &clusterctlv1.Metadata{
   106  					TypeMeta: metav1.TypeMeta{
   107  						APIVersion: clusterctlv1.GroupVersion.String(),
   108  						Kind:       "Metadata",
   109  					},
   110  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   111  						{Major: 1, Minor: 0, Contract: test.PreviousCAPIContractNotSupported},
   112  						{Major: 1, Minor: 1, Contract: test.CurrentCAPIContract},
   113  					},
   114  				},
   115  				currentVersion:  version.MustParseSemantic("v1.0.1"),
   116  				currentContract: test.PreviousCAPIContractNotSupported,
   117  				nextVersions: []version.Version{
   118  					// v1.0.1 (the current version) and older are ignored
   119  					*version.MustParseSemantic("v1.0.2"), // not supported, but upgrade plan should report these options
   120  					*version.MustParseSemantic("v1.1.0"),
   121  				},
   122  			},
   123  			wantErr: false,
   124  		},
   125  		{
   126  			name: "pass when current version is current contract, next version is in next contract", // upgrade plan should report unsupported options
   127  			fields: fields{
   128  				reader: test.NewFakeReader().
   129  					WithProvider("p1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"),
   130  				repo: repository.NewMemoryRepository().
   131  					WithVersions("v1.0.0", "v1.0.1", "v1.0.2", "v1.1.0").
   132  					WithMetadata("v1.1.0", &clusterctlv1.Metadata{
   133  						ReleaseSeries: []clusterctlv1.ReleaseSeries{
   134  							{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
   135  							{Major: 1, Minor: 1, Contract: test.NextCAPIContractNotSupported},
   136  						},
   137  					}),
   138  			},
   139  			args: args{
   140  				provider: fakeProvider("p1", clusterctlv1.InfrastructureProviderType, "v1.0.1", "p1-system"),
   141  			},
   142  			want: &upgradeInfo{
   143  				metadata: &clusterctlv1.Metadata{
   144  					TypeMeta: metav1.TypeMeta{
   145  						APIVersion: clusterctlv1.GroupVersion.String(),
   146  						Kind:       "Metadata",
   147  					},
   148  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   149  						{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
   150  						{Major: 1, Minor: 1, Contract: test.NextCAPIContractNotSupported},
   151  					},
   152  				},
   153  				currentVersion:  version.MustParseSemantic("v1.0.1"),
   154  				currentContract: test.CurrentCAPIContract,
   155  				nextVersions: []version.Version{
   156  					// v1.0.1 (the current version) and older are ignored
   157  					*version.MustParseSemantic("v1.0.2"),
   158  					*version.MustParseSemantic("v1.1.0"), // not supported, but upgrade plan should report these options
   159  				},
   160  			},
   161  			wantErr: false,
   162  		},
   163  		{
   164  			name: "fails if a metadata file for upgrades cannot be found",
   165  			fields: fields{
   166  				reader: test.NewFakeReader().
   167  					WithProvider("p1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"),
   168  				repo: repository.NewMemoryRepository(). // without metadata
   169  									WithVersions("v1.0.0", "v1.0.1"),
   170  			},
   171  			args: args{
   172  				provider: fakeProvider("p1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "p1-system"),
   173  			},
   174  			want:    nil,
   175  			wantErr: true,
   176  		},
   177  		{
   178  			name: "fails if a metadata file for upgrades cannot be found",
   179  			fields: fields{
   180  				reader: test.NewFakeReader().
   181  					WithProvider("p1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"),
   182  				repo: repository.NewMemoryRepository(). // with metadata but only for versions <= current version (not for next versions)
   183  									WithVersions("v1.0.0", "v1.0.1").
   184  									WithMetadata("v1.0.0", &clusterctlv1.Metadata{}),
   185  			},
   186  			args: args{
   187  				provider: fakeProvider("p1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "p1-system"),
   188  			},
   189  			want:    nil,
   190  			wantErr: true,
   191  		},
   192  		{
   193  			name: "fails if when current version does not match any release series in metadata",
   194  			fields: fields{
   195  				reader: test.NewFakeReader().
   196  					WithProvider("p1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"),
   197  				repo: repository.NewMemoryRepository(). // without metadata
   198  									WithVersions("v1.0.0", "v1.0.1").
   199  									WithMetadata("v1.0.1", &clusterctlv1.Metadata{}),
   200  			},
   201  			args: args{
   202  				provider: fakeProvider("p1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "p1-system"),
   203  			},
   204  			want:    nil,
   205  			wantErr: true,
   206  		},
   207  		{
   208  			name: "fails if available version does not match release series",
   209  			fields: fields{
   210  				reader: test.NewFakeReader().
   211  					WithProvider("p1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com"),
   212  				repo: repository.NewMemoryRepository(). // without metadata
   213  									WithVersions("v1.0.0", "v1.0.1", "v1.1.1").
   214  									WithMetadata("v1.1.1", &clusterctlv1.Metadata{
   215  						ReleaseSeries: []clusterctlv1.ReleaseSeries{
   216  							{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
   217  							// missing 1.1 series
   218  						},
   219  					}),
   220  			},
   221  			args: args{
   222  				provider: fakeProvider("p1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "p1-system"),
   223  			},
   224  			want:    nil,
   225  			wantErr: true,
   226  		},
   227  	}
   228  	for _, tt := range tests {
   229  		t.Run(tt.name, func(t *testing.T) {
   230  			g := NewWithT(t)
   231  
   232  			configClient, _ := config.New(context.Background(), "", config.InjectReader(tt.fields.reader))
   233  
   234  			u := &providerUpgrader{
   235  				configClient: configClient,
   236  				repositoryClientFactory: func(ctx context.Context, provider config.Provider, configClient config.Client, _ ...repository.Option) (repository.Client, error) {
   237  					return repository.New(ctx, provider, configClient, repository.InjectRepository(tt.fields.repo))
   238  				},
   239  			}
   240  			got, err := u.getUpgradeInfo(context.Background(), tt.args.provider)
   241  			if tt.wantErr {
   242  				g.Expect(err).To(HaveOccurred())
   243  			} else {
   244  				g.Expect(err).ToNot(HaveOccurred())
   245  			}
   246  			g.Expect(got).To(Equal(tt.want))
   247  		})
   248  	}
   249  }
   250  
   251  func Test_upgradeInfo_getContractsForUpgrade(t *testing.T) {
   252  	type field struct {
   253  		currentVersion string
   254  		metadata       *clusterctlv1.Metadata
   255  	}
   256  	tests := []struct {
   257  		name  string
   258  		field field
   259  		want  []string
   260  	}{
   261  		{
   262  			name: "One contract, current",
   263  			field: field{
   264  				metadata: &clusterctlv1.Metadata{ // metadata defining more release series, all linked to a single contract
   265  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   266  						{Major: 0, Minor: 1, Contract: test.CurrentCAPIContract},
   267  						{Major: 0, Minor: 2, Contract: test.CurrentCAPIContract},
   268  						{Major: 0, Minor: 3, Contract: test.CurrentCAPIContract},
   269  					},
   270  				},
   271  				currentVersion: "v0.2.1", // current version belonging of one of the above series
   272  			},
   273  			want: []string{test.CurrentCAPIContract},
   274  		},
   275  		{
   276  			name: "Multiple contracts (previous and current), all valid for upgrades", // upgrade plan should report unsupported options
   277  			field: field{
   278  				metadata: &clusterctlv1.Metadata{ // metadata defining more release series, linked to different contracts
   279  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   280  						{Major: 0, Minor: 1, Contract: test.PreviousCAPIContractNotSupported},
   281  						{Major: 0, Minor: 2, Contract: test.CurrentCAPIContract},
   282  					},
   283  				},
   284  				currentVersion: "v0.1.1", // current version linked to the first contract
   285  			},
   286  			want: []string{test.PreviousCAPIContractNotSupported, test.CurrentCAPIContract},
   287  		},
   288  		{
   289  			name: "Multiple contracts (current and next), all valid for upgrades", // upgrade plan should report unsupported options
   290  			field: field{
   291  				metadata: &clusterctlv1.Metadata{ // metadata defining more release series, linked to different contracts
   292  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   293  						{Major: 0, Minor: 1, Contract: test.CurrentCAPIContract},
   294  						{Major: 0, Minor: 2, Contract: test.NextCAPIContractNotSupported},
   295  					},
   296  				},
   297  				currentVersion: "v0.1.1", // current version linked to the first contract
   298  			},
   299  			want: []string{test.CurrentCAPIContract, test.NextCAPIContractNotSupported},
   300  		},
   301  		{
   302  			name: "Multiple contract supported (current and next), only one valid for upgrades", // upgrade plan should report unsupported options
   303  			field: field{
   304  				metadata: &clusterctlv1.Metadata{ // metadata defining more release series, linked to different contracts
   305  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   306  						{Major: 0, Minor: 1, Contract: test.PreviousCAPIContractNotSupported},
   307  						{Major: 0, Minor: 2, Contract: test.CurrentCAPIContract},
   308  					},
   309  				},
   310  				currentVersion: "v0.2.1", // current version linked to the second/the last contract, so the first one is not anymore valid for upgrades
   311  			},
   312  			want: []string{test.CurrentCAPIContract},
   313  		},
   314  		{
   315  			name: "Current version does not match the release series",
   316  			field: field{
   317  				metadata:       &clusterctlv1.Metadata{},
   318  				currentVersion: "v0.2.1",
   319  			},
   320  			want: []string{},
   321  		},
   322  	}
   323  	for _, tt := range tests {
   324  		t.Run(tt.name, func(t *testing.T) {
   325  			g := NewWithT(t)
   326  
   327  			upgradeInfo := newUpgradeInfo(tt.field.metadata, version.MustParseSemantic(tt.field.currentVersion), nil)
   328  
   329  			got := upgradeInfo.getContractsForUpgrade()
   330  			g.Expect(got).To(Equal(tt.want))
   331  		})
   332  	}
   333  }
   334  
   335  func Test_upgradeInfo_getLatestNextVersion(t *testing.T) {
   336  	type field struct {
   337  		currentVersion string
   338  		nextVersions   []string
   339  		metadata       *clusterctlv1.Metadata
   340  	}
   341  	type args struct {
   342  		contract string
   343  	}
   344  	tests := []struct {
   345  		name  string
   346  		field field
   347  		args  args
   348  		want  string
   349  	}{
   350  		{
   351  			name: "Already up-to-date, no upgrade version",
   352  			field: field{
   353  				currentVersion: "v1.2.3",
   354  				nextVersions:   []string{}, // Next versions empty
   355  				metadata: &clusterctlv1.Metadata{
   356  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   357  						{Major: 1, Minor: 2, Contract: test.CurrentCAPIContract},
   358  					},
   359  				},
   360  			},
   361  			args: args{
   362  				contract: test.CurrentCAPIContract,
   363  			},
   364  			want: "",
   365  		},
   366  		{
   367  			name: "Find an upgrade version in the same release series, current contract",
   368  			field: field{
   369  				currentVersion: "v1.2.3",
   370  				nextVersions:   []string{"v1.2.4", "v1.2.5"},
   371  				metadata: &clusterctlv1.Metadata{
   372  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   373  						{Major: 1, Minor: 2, Contract: test.CurrentCAPIContract},
   374  					},
   375  				},
   376  			},
   377  			args: args{
   378  				contract: test.CurrentCAPIContract,
   379  			},
   380  			want: "v1.2.5", // skipping v1.2.4 because it is not the latest version available
   381  		},
   382  		{
   383  			name: "Find an upgrade version in the next release series, current contract",
   384  			field: field{
   385  				currentVersion: "v1.2.3",
   386  				nextVersions:   []string{"v1.2.4", "v1.3.1", "v2.0.1", "v2.0.2"},
   387  				metadata: &clusterctlv1.Metadata{
   388  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   389  						{Major: 1, Minor: 2, Contract: test.CurrentCAPIContract},
   390  						{Major: 1, Minor: 3, Contract: test.CurrentCAPIContract},
   391  						{Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported},
   392  					},
   393  				},
   394  			},
   395  			args: args{
   396  				contract: test.CurrentCAPIContract,
   397  			},
   398  			want: "v1.3.1", // skipping v1.2.4 because it is not the latest version available; ignoring v2.0.* because linked to a different contract
   399  		},
   400  		{
   401  			name: "Find an upgrade version in the next contract", // upgrade plan should report unsupported options
   402  			field: field{
   403  				currentVersion: "v1.2.3",
   404  				nextVersions:   []string{"v1.2.4", "v1.3.1", "v2.0.1", "v2.0.2"},
   405  				metadata: &clusterctlv1.Metadata{
   406  					ReleaseSeries: []clusterctlv1.ReleaseSeries{
   407  						{Major: 1, Minor: 2, Contract: test.CurrentCAPIContract},
   408  						{Major: 1, Minor: 3, Contract: test.CurrentCAPIContract},
   409  						{Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported},
   410  					},
   411  				},
   412  			},
   413  			args: args{
   414  				contract: test.NextCAPIContractNotSupported,
   415  			},
   416  			want: "v2.0.2", // skipping v2.0.1 because it is not the latest version available; ignoring v1.* because linked to a different contract
   417  		},
   418  	}
   419  	for _, tt := range tests {
   420  		t.Run(tt.name, func(t *testing.T) {
   421  			g := NewWithT(t)
   422  
   423  			upgradeInfo := newUpgradeInfo(tt.field.metadata, version.MustParseSemantic(tt.field.currentVersion), toSemanticVersions(tt.field.nextVersions))
   424  
   425  			got := upgradeInfo.getLatestNextVersion(tt.args.contract)
   426  			g.Expect(versionTag(got)).To(Equal(tt.want))
   427  		})
   428  	}
   429  }
   430  
   431  func toSemanticVersions(versions []string) []version.Version {
   432  	semanticVersions := []version.Version{}
   433  	for _, v := range versions {
   434  		semanticVersions = append(semanticVersions, *version.MustParseSemantic(v))
   435  	}
   436  	return semanticVersions
   437  }
   438  
   439  func fakeProvider(name string, providerType clusterctlv1.ProviderType, version, targetNamespace string) clusterctlv1.Provider {
   440  	return clusterctlv1.Provider{
   441  		TypeMeta: metav1.TypeMeta{
   442  			APIVersion: clusterctlv1.GroupVersion.String(),
   443  			Kind:       "Provider",
   444  		},
   445  		ObjectMeta: metav1.ObjectMeta{
   446  			ResourceVersion: "999",
   447  			Namespace:       targetNamespace,
   448  			Name:            clusterctlv1.ManifestLabel(name, providerType),
   449  			Labels: map[string]string{
   450  				clusterctlv1.ClusterctlLabel:     "",
   451  				clusterv1.ProviderNameLabel:      clusterctlv1.ManifestLabel(name, providerType),
   452  				clusterctlv1.ClusterctlCoreLabel: clusterctlv1.ClusterctlCoreLabelInventoryValue,
   453  			},
   454  		},
   455  		ProviderName: name,
   456  		Type:         string(providerType),
   457  		Version:      version,
   458  	}
   459  }