Skip to content

Commit cae6d82

Browse files
Refactor hooks call in computeControlPlaneVersion, add Lifecycle hooks sequence tests
1 parent ec9190e commit cae6d82

File tree

4 files changed

+1287
-1504
lines changed

4 files changed

+1287
-1504
lines changed

exp/topology/desiredstate/desired_state.go

Lines changed: 12 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,8 @@ package desiredstate
1919

2020
import (
2121
"context"
22-
"fmt"
2322
"maps"
2423
"reflect"
25-
"slices"
26-
"strings"
2724
"time"
2825

2926
"github.com/pkg/errors"
@@ -33,15 +30,13 @@ import (
3330
"k8s.io/apimachinery/pkg/runtime/schema"
3431
"k8s.io/klog/v2"
3532
"k8s.io/utils/ptr"
36-
ctrl "sigs.k8s.io/controller-runtime"
3733
"sigs.k8s.io/controller-runtime/pkg/client"
3834

3935
clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
4036
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
4137
runtimehooksv1 "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1"
4238
"sigs.k8s.io/cluster-api/controllers/clustercache"
4339
"sigs.k8s.io/cluster-api/controllers/external"
44-
runtimecatalog "sigs.k8s.io/cluster-api/exp/runtime/catalog"
4540
runtimeclient "sigs.k8s.io/cluster-api/exp/runtime/client"
4641
"sigs.k8s.io/cluster-api/exp/topology/scope"
4742
"sigs.k8s.io/cluster-api/feature"
@@ -507,7 +502,6 @@ func (g *generator) computeControlPlane(ctx context.Context, s *scope.Scope, inf
507502
// The version is calculated using the state of the current machine deployments, the current control plane
508503
// and the version defined in the topology.
509504
func (g *generator) computeControlPlaneVersion(ctx context.Context, s *scope.Scope) (string, error) {
510-
log := ctrl.LoggerFrom(ctx)
511505
topologyVersion := s.Blueprint.Topology.Version
512506
// If we are creating the control plane object (current control plane is nil), use version from topology.
513507
if s.Current.ControlPlane == nil || s.Current.ControlPlane.Object == nil {
@@ -560,43 +554,12 @@ func (g *generator) computeControlPlaneVersion(ctx context.Context, s *scope.Sco
560554
// if the control plane is not upgrading, before making further considerations about if to pick up another version,
561555
// we should call the AfterControlPlaneUpgrade hook if not already done.
562556
if feature.Gates.Enabled(feature.RuntimeSDK) {
563-
// Call the hook only if we are tracking the intent to do so. If it is not tracked it means we don't need to call the
564-
// hook because we didn't go through an upgrade or we already called the hook after the upgrade.
565-
if hooks.IsPending(runtimehooksv1.AfterControlPlaneUpgrade, s.Current.Cluster) {
566-
v1beta1Cluster := &clusterv1beta1.Cluster{}
567-
// DeepCopy cluster because ConvertFrom has side effects like adding the conversion annotation.
568-
if err := v1beta1Cluster.ConvertFrom(s.Current.Cluster.DeepCopy()); err != nil {
569-
return "", errors.Wrap(err, "error converting Cluster to v1beta1 Cluster")
570-
}
571-
572-
// Call all the registered extension for the hook.
573-
hookRequest := &runtimehooksv1.AfterControlPlaneUpgradeRequest{
574-
Cluster: *cleanupCluster(v1beta1Cluster),
575-
KubernetesVersion: *currentVersion,
576-
ControlPlaneUpgrades: toUpgradeStep(s.UpgradeTracker.ControlPlane.UpgradePlan),
577-
WorkersUpgrades: toUpgradeStep(s.UpgradeTracker.MachineDeployments.UpgradePlan, s.UpgradeTracker.MachinePools.UpgradePlan),
578-
}
579-
hookResponse := &runtimehooksv1.AfterControlPlaneUpgradeResponse{}
580-
if err := g.RuntimeClient.CallAllExtensions(ctx, runtimehooksv1.AfterControlPlaneUpgrade, s.Current.Cluster, hookRequest, hookResponse); err != nil {
581-
return "", err
582-
}
583-
// Add the response to the tracker so we can later update condition or requeue when required.
584-
s.HookResponseTracker.Add(runtimehooksv1.AfterControlPlaneUpgrade, hookResponse)
585-
586-
// If the extension responds to hold off on starting Machine deployments upgrades,
587-
// change the UpgradeTracker accordingly, otherwise the hook call is completed and we
588-
// can remove this hook from the list of pending-hooks.
589-
if hookResponse.RetryAfterSeconds != 0 {
590-
v := topologyVersion
591-
if len(s.UpgradeTracker.ControlPlane.UpgradePlan) > 0 {
592-
v = s.UpgradeTracker.ControlPlane.UpgradePlan[0]
593-
}
594-
log.Info(fmt.Sprintf("Upgrade to version %q is blocked by %q hook", v, runtimecatalog.HookName(runtimehooksv1.AfterControlPlaneUpgrade)))
595-
return *currentVersion, nil
596-
}
597-
if err := hooks.MarkAsDone(ctx, g.Client, s.Current.Cluster, runtimehooksv1.AfterControlPlaneUpgrade); err != nil {
598-
return "", err
599-
}
557+
hookCompleted, err := g.callAfterControlPlaneUpgradeHook(ctx, s, currentVersion, topologyVersion)
558+
if err != nil {
559+
return "", err
560+
}
561+
if !hookCompleted {
562+
return *currentVersion, nil
600563
}
601564
}
602565

@@ -634,64 +597,12 @@ func (g *generator) computeControlPlaneVersion(ctx context.Context, s *scope.Sco
634597
// are not upgrading/are not required to upgrade.
635598
// If not already done, call the BeforeClusterUpgrade hook before picking up the desired version.
636599
if feature.Gates.Enabled(feature.RuntimeSDK) {
637-
// NOTE: the hook should be called only at the beginning of either a regular upgrade or a multistep upgrade sequence (it should not be called when in the middle of a multistep upgrade sequence);
638-
// to detect if we are at the beginning of an upgrade, we check if the intent to call the AfterClusterUpgrade is not yet tracked.
639-
if !hooks.IsPending(runtimehooksv1.AfterClusterUpgrade, s.Current.Cluster) {
640-
var hookAnnotations []string
641-
for key := range s.Current.Cluster.Annotations {
642-
if strings.HasPrefix(key, clusterv1.BeforeClusterUpgradeHookAnnotationPrefix) {
643-
hookAnnotations = append(hookAnnotations, key)
644-
}
645-
}
646-
if len(hookAnnotations) > 0 {
647-
slices.Sort(hookAnnotations)
648-
message := fmt.Sprintf("annotations [%s] are set", strings.Join(hookAnnotations, ", "))
649-
if len(hookAnnotations) == 1 {
650-
message = fmt.Sprintf("annotation [%s] is set", strings.Join(hookAnnotations, ", "))
651-
}
652-
// Add the hook with a response to the tracker so we can later update the condition.
653-
s.HookResponseTracker.Add(runtimehooksv1.BeforeClusterUpgrade, &runtimehooksv1.BeforeClusterUpgradeResponse{
654-
CommonRetryResponse: runtimehooksv1.CommonRetryResponse{
655-
// RetryAfterSeconds needs to be set because having only hooks without RetryAfterSeconds
656-
// would lead to not updating the condition. We can rely on getting an event when the
657-
// annotation gets removed so we set twice of the default sync-period to not cause additional reconciles.
658-
RetryAfterSeconds: 20 * 60,
659-
CommonResponse: runtimehooksv1.CommonResponse{
660-
Message: message,
661-
},
662-
},
663-
})
664-
665-
log.Info(fmt.Sprintf("Cluster upgrade to version %q is blocked by %q hook (via annotations)", topologyVersion, runtimecatalog.HookName(runtimehooksv1.BeforeClusterUpgrade)), "hooks", strings.Join(hookAnnotations, ","))
666-
return *currentVersion, nil
667-
}
668-
669-
// At this point the control plane and the machine deployments are stable and we are almost ready to pick
670-
// up the topologyVersion. Call the BeforeClusterUpgrade hook before picking up the desired version.
671-
v1beta1Cluster := &clusterv1beta1.Cluster{}
672-
// DeepCopy cluster because ConvertFrom has side effects like adding the conversion annotation.
673-
if err := v1beta1Cluster.ConvertFrom(s.Current.Cluster.DeepCopy()); err != nil {
674-
return "", errors.Wrap(err, "error converting Cluster to v1beta1 Cluster")
675-
}
676-
677-
hookRequest := &runtimehooksv1.BeforeClusterUpgradeRequest{
678-
Cluster: *cleanupCluster(v1beta1Cluster),
679-
FromKubernetesVersion: *currentVersion,
680-
ToKubernetesVersion: topologyVersion,
681-
ControlPlaneUpgrades: toUpgradeStep(s.UpgradeTracker.ControlPlane.UpgradePlan),
682-
WorkersUpgrades: toUpgradeStep(s.UpgradeTracker.MachineDeployments.UpgradePlan, s.UpgradeTracker.MachinePools.UpgradePlan),
683-
}
684-
hookResponse := &runtimehooksv1.BeforeClusterUpgradeResponse{}
685-
if err := g.RuntimeClient.CallAllExtensions(ctx, runtimehooksv1.BeforeClusterUpgrade, s.Current.Cluster, hookRequest, hookResponse); err != nil {
686-
return "", err
687-
}
688-
// Add the response to the tracker so we can later update condition or requeue when required.
689-
s.HookResponseTracker.Add(runtimehooksv1.BeforeClusterUpgrade, hookResponse)
690-
if hookResponse.RetryAfterSeconds != 0 {
691-
// Cannot pickup the new version right now. Need to try again later.
692-
log.Info(fmt.Sprintf("Cluster upgrade to version %q is blocked by %q hook", topologyVersion, runtimecatalog.HookName(runtimehooksv1.BeforeClusterUpgrade)))
693-
return *currentVersion, nil
694-
}
600+
hookCompleted, err := g.callBeforeClusterUpgradeHook(ctx, s, currentVersion, topologyVersion)
601+
if err != nil {
602+
return "", err
603+
}
604+
if !hookCompleted {
605+
return *currentVersion, nil
695606
}
696607
}
697608

@@ -1660,18 +1571,3 @@ func cleanupCluster(cluster *clusterv1beta1.Cluster) *clusterv1beta1.Cluster {
16601571
cluster.Status = clusterv1beta1.ClusterStatus{}
16611572
return cluster
16621573
}
1663-
1664-
// toUpgradeStep converts a list of version to a list of upgrade steps.
1665-
// Note. when called for workers, the function will receive in input two plans one for the MachineDeployments if any, the other for MachinePools if any.
1666-
// Considering that both plans, if defined, have to be equal, the function picks the first one not empty.
1667-
func toUpgradeStep(plans ...[]string) []runtimehooksv1.UpgradeStep {
1668-
var steps []runtimehooksv1.UpgradeStep
1669-
for _, plan := range plans {
1670-
if len(plan) != 0 {
1671-
for _, step := range plan {
1672-
steps = append(steps, runtimehooksv1.UpgradeStep{Version: step})
1673-
}
1674-
}
1675-
}
1676-
return steps
1677-
}

0 commit comments

Comments
 (0)