Skip to content

Commit 42f28dc

Browse files
authored
Merge pull request #11970 from k8s-infra-cherrypick-robot/cherry-pick-11940-to-release-1.9
[release-1.9] 🐛 ClusterClass: Don't allow concurrent patch upgrades
2 parents 1f0063d + 480c1fe commit 42f28dc

File tree

2 files changed

+43
-35
lines changed

2 files changed

+43
-35
lines changed

Diff for: internal/webhooks/cluster.go

+26-35
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
"k8s.io/apimachinery/pkg/util/validation"
3333
"k8s.io/apimachinery/pkg/util/validation/field"
3434
"k8s.io/apimachinery/pkg/util/wait"
35-
"k8s.io/klog/v2"
3635
ctrl "sigs.k8s.io/controller-runtime"
3736
"sigs.k8s.io/controller-runtime/pkg/client"
3837
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -446,6 +445,11 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
446445
}
447446

448447
func (webhook *Cluster) validateTopologyVersion(ctx context.Context, fldPath *field.Path, fldValue string, inVersion, oldVersion semver.Version, oldCluster *clusterv1.Cluster) *field.Error {
448+
// Nothing to do if the version doesn't change.
449+
if version.Compare(inVersion, oldVersion, version.WithBuildTags()) == 0 {
450+
return nil
451+
}
452+
449453
// Version could only be increased.
450454
if inVersion.NE(semver.Version{}) && oldVersion.NE(semver.Version{}) && version.Compare(inVersion, oldVersion, version.WithBuildTags()) == -1 {
451455
return field.Invalid(
@@ -469,39 +473,27 @@ func (webhook *Cluster) validateTopologyVersion(ctx context.Context, fldPath *fi
469473
)
470474
}
471475

472-
// Only check the following cases if the minor version increases by 1 (we already return above for >= 2).
473-
ceilVersion = semver.Version{
474-
Major: oldVersion.Major,
475-
Minor: oldVersion.Minor + 1,
476-
Patch: 0,
477-
}
478-
479-
// Return early if its not a minor version upgrade.
480-
if !inVersion.GTE(ceilVersion) {
481-
return nil
482-
}
483-
484476
allErrs := []error{}
485477
// minor version cannot be increased if control plane is upgrading or not yet on the current version
486478
if err := validateTopologyControlPlaneVersion(ctx, webhook.Client, oldCluster, oldVersion); err != nil {
487-
allErrs = append(allErrs, fmt.Errorf("blocking version update due to ControlPlane version check: %v", err))
479+
allErrs = append(allErrs, err)
488480
}
489481

490482
// minor version cannot be increased if MachineDeployments are upgrading or not yet on the current version
491483
if err := validateTopologyMachineDeploymentVersions(ctx, webhook.Client, oldCluster, oldVersion); err != nil {
492-
allErrs = append(allErrs, fmt.Errorf("blocking version update due to MachineDeployment version check: %v", err))
484+
allErrs = append(allErrs, err)
493485
}
494486

495487
// minor version cannot be increased if MachinePools are upgrading or not yet on the current version
496488
if err := validateTopologyMachinePoolVersions(ctx, webhook.Client, webhook.ClusterCacheReader, oldCluster, oldVersion); err != nil {
497-
allErrs = append(allErrs, fmt.Errorf("blocking version update due to MachinePool version check: %v", err))
489+
allErrs = append(allErrs, err)
498490
}
499491

500492
if len(allErrs) > 0 {
501493
return field.Invalid(
502494
fldPath,
503495
fldValue,
504-
fmt.Sprintf("minor version update cannot happen at this time: %v", kerrors.NewAggregate(allErrs)),
496+
fmt.Sprintf("version cannot be changed: %v", kerrors.NewAggregate(allErrs)),
505497
)
506498
}
507499

@@ -511,39 +503,39 @@ func (webhook *Cluster) validateTopologyVersion(ctx context.Context, fldPath *fi
511503
func validateTopologyControlPlaneVersion(ctx context.Context, ctrlClient client.Reader, oldCluster *clusterv1.Cluster, oldVersion semver.Version) error {
512504
cp, err := external.Get(ctx, ctrlClient, oldCluster.Spec.ControlPlaneRef)
513505
if err != nil {
514-
return errors.Wrap(err, "failed to get ControlPlane object")
506+
return errors.Wrap(err, "failed to check if control plane is upgrading: failed to get control plane object")
515507
}
516508

517509
cpVersionString, err := contract.ControlPlane().Version().Get(cp)
518510
if err != nil {
519-
return errors.Wrap(err, "failed to get ControlPlane version")
511+
return errors.Wrap(err, "failed to check if control plane is upgrading: failed to get control plane version")
520512
}
521513

522514
cpVersion, err := semver.ParseTolerant(*cpVersionString)
523515
if err != nil {
524516
// NOTE: this should never happen. Nevertheless, handling this for extra caution.
525-
return errors.New("failed to parse version of ControlPlane")
517+
return errors.Wrapf(err, "failed to check if control plane is upgrading: failed to parse control plane version %s", *cpVersionString)
526518
}
527519
if cpVersion.NE(oldVersion) {
528-
return fmt.Errorf("ControlPlane version %q does not match the current version %q", cpVersion, oldVersion)
520+
return fmt.Errorf("Cluster.spec.topology.version %s was not propagated to control plane yet (control plane version %s)", oldVersion, cpVersion) //nolint:stylecheck // capitalization is intentional
529521
}
530522

531523
provisioning, err := contract.ControlPlane().IsProvisioning(cp)
532524
if err != nil {
533-
return errors.Wrap(err, "failed to check if ControlPlane is provisioning")
525+
return errors.Wrap(err, "failed to check if control plane is provisioning")
534526
}
535527

536528
if provisioning {
537-
return errors.New("ControlPlane is currently provisioning")
529+
return errors.New("control plane is currently provisioning")
538530
}
539531

540532
upgrading, err := contract.ControlPlane().IsUpgrading(cp)
541533
if err != nil {
542-
return errors.Wrap(err, "failed to check if ControlPlane is upgrading")
534+
return errors.Wrap(err, "failed to check if control plane is upgrading")
543535
}
544536

545537
if upgrading {
546-
return errors.New("ControlPlane is still completing a previous upgrade")
538+
return errors.New("control plane is still completing a previous upgrade")
547539
}
548540

549541
return nil
@@ -561,7 +553,7 @@ func validateTopologyMachineDeploymentVersions(ctx context.Context, ctrlClient c
561553
client.InNamespace(oldCluster.Namespace),
562554
)
563555
if err != nil {
564-
return errors.Wrap(err, "failed to read MachineDeployments for managed topology")
556+
return errors.Wrap(err, "failed to check if MachineDeployments are upgrading: failed to get MachineDeployments")
565557
}
566558

567559
if len(mds.Items) == 0 {
@@ -576,7 +568,7 @@ func validateTopologyMachineDeploymentVersions(ctx context.Context, ctrlClient c
576568
mdVersion, err := semver.ParseTolerant(*md.Spec.Template.Spec.Version)
577569
if err != nil {
578570
// NOTE: this should never happen. Nevertheless, handling this for extra caution.
579-
return errors.Wrapf(err, "failed to parse MachineDeployment's %q version %q", klog.KObj(md), *md.Spec.Template.Spec.Version)
571+
return errors.Wrapf(err, "failed to check if MachineDeployment %s is upgrading: failed to parse version %s", md.Name, *md.Spec.Template.Spec.Version)
580572
}
581573

582574
if mdVersion.NE(oldVersion) {
@@ -586,15 +578,15 @@ func validateTopologyMachineDeploymentVersions(ctx context.Context, ctrlClient c
586578

587579
upgrading, err := check.IsMachineDeploymentUpgrading(ctx, ctrlClient, md)
588580
if err != nil {
589-
return errors.Wrap(err, "failed to check if MachineDeployment is upgrading")
581+
return err
590582
}
591583
if upgrading {
592584
mdUpgradingNames = append(mdUpgradingNames, md.Name)
593585
}
594586
}
595587

596588
if len(mdUpgradingNames) > 0 {
597-
return fmt.Errorf("there are MachineDeployments still completing a previous upgrade: [%s]", strings.Join(mdUpgradingNames, ", "))
589+
return fmt.Errorf("there are still MachineDeployments completing a previous upgrade: [%s]", strings.Join(mdUpgradingNames, ", "))
598590
}
599591

600592
return nil
@@ -612,17 +604,16 @@ func validateTopologyMachinePoolVersions(ctx context.Context, ctrlClient client.
612604
client.InNamespace(oldCluster.Namespace),
613605
)
614606
if err != nil {
615-
return errors.Wrap(err, "failed to read MachinePools for managed topology")
607+
return errors.Wrap(err, "failed to check if MachinePools are upgrading: failed to get MachinePools")
616608
}
617609

618-
// Return early
619610
if len(mps.Items) == 0 {
620611
return nil
621612
}
622613

623614
wlClient, err := clusterCacheReader.GetReader(ctx, client.ObjectKeyFromObject(oldCluster))
624615
if err != nil {
625-
return errors.Wrap(err, "unable to get client for workload cluster")
616+
return errors.Wrap(err, "failed to check if MachinePools are upgrading: unable to get client for workload cluster")
626617
}
627618

628619
mpUpgradingNames := []string{}
@@ -633,7 +624,7 @@ func validateTopologyMachinePoolVersions(ctx context.Context, ctrlClient client.
633624
mpVersion, err := semver.ParseTolerant(*mp.Spec.Template.Spec.Version)
634625
if err != nil {
635626
// NOTE: this should never happen. Nevertheless, handling this for extra caution.
636-
return errors.Wrapf(err, "failed to parse MachinePool's %q version %q", klog.KObj(mp), *mp.Spec.Template.Spec.Version)
627+
return errors.Wrapf(err, "failed to check if MachinePool %s is upgrading: failed to parse version %s", mp.Name, *mp.Spec.Template.Spec.Version)
637628
}
638629

639630
if mpVersion.NE(oldVersion) {
@@ -643,15 +634,15 @@ func validateTopologyMachinePoolVersions(ctx context.Context, ctrlClient client.
643634

644635
upgrading, err := check.IsMachinePoolUpgrading(ctx, wlClient, mp)
645636
if err != nil {
646-
return errors.Wrap(err, "failed to check if MachinePool is upgrading")
637+
return err
647638
}
648639
if upgrading {
649640
mpUpgradingNames = append(mpUpgradingNames, mp.Name)
650641
}
651642
}
652643

653644
if len(mpUpgradingNames) > 0 {
654-
return fmt.Errorf("there are MachinePools still completing a previous upgrade: [%s]", strings.Join(mpUpgradingNames, ", "))
645+
return fmt.Errorf("there are still MachinePools completing a previous upgrade: [%s]", strings.Join(mpUpgradingNames, ", "))
655646
}
656647

657648
return nil

Diff for: internal/webhooks/cluster_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -1833,6 +1833,7 @@ func TestClusterTopologyValidation(t *testing.T) {
18331833
name: "should update",
18341834
expectErr: false,
18351835
old: builder.Cluster("fooboo", "cluster1").
1836+
WithControlPlane(builder.ControlPlane("fooboo", "cluster1-cp").Build()).
18361837
WithTopology(builder.ClusterTopology().
18371838
WithClass("foo").
18381839
WithVersion("v1.19.1").
@@ -1855,6 +1856,7 @@ func TestClusterTopologyValidation(t *testing.T) {
18551856
Build()).
18561857
Build(),
18571858
in: builder.Cluster("fooboo", "cluster1").
1859+
WithControlPlane(builder.ControlPlane("fooboo", "cluster1-cp").Build()).
18581860
WithTopology(builder.ClusterTopology().
18591861
WithClass("foo").
18601862
WithVersion("v1.19.2").
@@ -1876,6 +1878,21 @@ func TestClusterTopologyValidation(t *testing.T) {
18761878
Build()).
18771879
Build()).
18781880
Build(),
1881+
additionalObjects: []client.Object{
1882+
builder.ControlPlane("fooboo", "cluster1-cp").WithVersion("v1.19.1").
1883+
WithStatusFields(map[string]interface{}{"status.version": "v1.19.1"}).
1884+
Build(),
1885+
builder.MachineDeployment("fooboo", "cluster1-workers1").WithLabels(map[string]string{
1886+
clusterv1.ClusterNameLabel: "cluster1",
1887+
clusterv1.ClusterTopologyOwnedLabel: "",
1888+
clusterv1.ClusterTopologyMachineDeploymentNameLabel: "workers1",
1889+
}).WithVersion("v1.19.1").Build(),
1890+
builder.MachinePool("fooboo", "cluster1-pool1").WithLabels(map[string]string{
1891+
clusterv1.ClusterNameLabel: "cluster1",
1892+
clusterv1.ClusterTopologyOwnedLabel: "",
1893+
clusterv1.ClusterTopologyMachinePoolNameLabel: "pool1",
1894+
}).WithVersion("v1.19.1").Build(),
1895+
},
18791896
},
18801897
{
18811898
name: "should return error when upgrade concurrency annotation value is < 1",

0 commit comments

Comments
 (0)