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 }