Skip to content
This repository has been archived by the owner on Oct 10, 2023. It is now read-only.

Commit

Permalink
UpdatesAvailable condition for legacy-style Clusters in uTKG (#2997)
Browse files Browse the repository at this point in the history
* UpdatesAvailable condition for legacy-style Clusters in uTKG

Implement #2979.

Signed-off-by: Ivan Mikushin <[email protected]>

* fixup! UpdatesAvailable condition for legacy-style Clusters in uTKG

Signed-off-by: Ivan Mikushin <[email protected]>
  • Loading branch information
imikushin authored Jul 26, 2022
1 parent 3e9c9f2 commit e9cd07f
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 72 deletions.
2 changes: 1 addition & 1 deletion apis/run/v1alpha3/tanzukubernetesrelease_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const (

ConditionUpdatesAvailable = "UpdatesAvailable"

ReasonTKRNotFound = "TKRNotFound"
ReasonCannotParseTKR = "CannotParseTKR"
ReasonAlreadyUpToDate = "AlreadyUpToDate"

LabelIncompatible = "incompatible"
Expand Down
103 changes: 69 additions & 34 deletions pkg/v2/tkr/controller/tkr-status/clusterstatus/clusterstatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/types"
kerrors "k8s.io/apimachinery/pkg/util/errors"
utilversion "k8s.io/apimachinery/pkg/util/version"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util/conditions"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -37,19 +37,19 @@ import (
"github.com/vmware-tanzu/tanzu-framework/pkg/v2/tkr/util/version"
)

const LegacyClusterTKRLabel = "tanzuKubernetesRelease"

type Reconciler struct {
Log logr.Logger
Client client.Client
TKRResolver resolver.CachingResolver
Context context.Context
}

var hasTKRLabel = func() predicate.Predicate {
selector, _ := labels.Parse(runv1.LabelTKR)
return predicate.NewPredicateFuncs(func(o client.Object) bool {
return selector.Matches(labels.Set(o.GetLabels()))
})
}()
var hasTKRLabel = predicate.NewPredicateFuncs(func(o client.Object) bool {
ls := labels.Set(o.GetLabels())
return ls.Has(runv1.LabelTKR) || ls.Has(LegacyClusterTKRLabel)
})

const indexCanUpdateToVersion = ".index.canUpdateToVersion"

Expand Down Expand Up @@ -137,47 +137,37 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
}

func (r *Reconciler) calculateAndSetUpdatesAvailable(ctx context.Context, cluster *clusterv1.Cluster) error {
tkrName := cluster.Labels[runv1.LabelTKR]
clusterClass, err := topology.GetClusterClass(ctx, r.Client, cluster)
if err != nil {
return err
}
if clusterClass == nil {
return nil // do not set UpdatesAvailable condition if clusterClass is not set
tkrName = cluster.Labels[LegacyClusterTKRLabel]
}

tkrName := cluster.Labels[runv1.LabelTKR]
tkr := r.TKRResolver.Get(tkrName, &runv1.TanzuKubernetesRelease{}).(*runv1.TanzuKubernetesRelease)
if tkr == nil {
conditions.MarkUnknown(cluster, runv1.ConditionUpdatesAvailable, runv1.ReasonTKRNotFound, "TKR '%s' is not found", tkrName)
tkrVersion, err := version.ParseSemantic(version.FromLabel(tkrName))
if err != nil {
conditions.MarkUnknown(cluster, runv1.ConditionUpdatesAvailable, runv1.ReasonCannotParseTKR, "Cannot parse TKR version from TKR name '%s': %s", tkrName, err)
return nil
}
updates, err := r.updatesAvailable(tkr, cluster, clusterClass)
updates, err := r.updatesAvailable(ctx, tkrVersion, cluster, clusterClass)
if err != nil {
return err
}
if len(updates) == 0 {
conditions.MarkFalse(cluster, runv1.ConditionUpdatesAvailable, runv1.ReasonAlreadyUpToDate, clusterv1.ConditionSeverityInfo, "")
return nil
}
updatesAvailableCondition := conditions.TrueCondition(runv1.ConditionUpdatesAvailable)
updatesAvailableCondition.Message = fmt.Sprintf("%v", updates)
conditions.Set(cluster, updatesAvailableCondition)
setUpdatesAvailable(cluster, updates)
return nil
}

func (r *Reconciler) updatesAvailable(tkr *runv1.TanzuKubernetesRelease, cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass) ([]string, error) {
func (r *Reconciler) updatesAvailable(ctx context.Context, tkrVersion *version.Version, cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass) ([]string, error) {
var result []string
sv, err := utilversion.ParseSemantic(tkr.Spec.Kubernetes.Version)
if err != nil {
return nil, err
}
major, minor := sv.Major(), sv.Minor()
major, minor := tkrVersion.Major(), tkrVersion.Minor()

for _, versionPrefix := range []string{
vLabelMinor(major, minor),
vLabelMinor(major, minor+1),
} {
updateVersions, err := r.findUpdateVersion(tkr, cluster, clusterClass, versionPrefix)
updateVersions, err := r.findUpdateVersions(ctx, tkrVersion, cluster, clusterClass, versionPrefix)
if err != nil {
return nil, err
}
Expand All @@ -187,20 +177,59 @@ func (r *Reconciler) updatesAvailable(tkr *runv1.TanzuKubernetesRelease, cluster
return result, nil
}

func (r *Reconciler) findUpdateVersion(tkr *runv1.TanzuKubernetesRelease, cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass, versionPrefix string) ([]string, error) {
func vLabelMinor(major, minor uint) string {
return fmt.Sprintf("v%v.%v", major, minor)
}

func (r *Reconciler) findUpdateVersions(ctx context.Context, tkrVersion *version.Version, cluster *clusterv1.Cluster, clusterClass *clusterv1.ClusterClass, versionPrefix string) ([]string, error) {
if clusterClass == nil {
return r.legacyUpdateTKRVersions(ctx, tkrVersion, versionPrefix)
}

query, err := resolution.ConstructQuery(versionPrefix, cluster, clusterClass)
if err != nil {
return nil, err
}
tkrResult := r.TKRResolver.Resolve(*query)
if tkrResult.ControlPlane.K8sVersion != "" && tkrResult.ControlPlane.TKRName != tkr.Name {
return resolvedTKRVersions(tkr, tkrResult)
if tkrResult.ControlPlane.K8sVersion != "" && tkrResult.ControlPlane.TKRName != version.Label(tkrVersion.String()) {
return resolvedTKRVersions(tkrVersion, tkrResult)
}
return nil, nil
}

func resolvedTKRVersions(currentTKR *runv1.TanzuKubernetesRelease, tkrResult data.Result) ([]string, error) {
currentTKRVersion, _ := version.ParseSemantic(currentTKR.Spec.Version)
var activeAndCompatible = func() labels.Selector {
selector, err := labels.Parse("!deactivated,!incompatible")
if err != nil {
panic(err)
}
return selector
}()

func (r *Reconciler) legacyUpdateTKRVersions(ctx context.Context, currentTKRVersion *version.Version, versionPrefix string) ([]string, error) {
tkrList := &runv1.TanzuKubernetesReleaseList{}
hasVersionPrefix, _ := labels.NewRequirement(version.Label(versionPrefix), selection.Exists, nil)
doesNotHaveLabel, _ := labels.NewRequirement(version.Label(currentTKRVersion.String()), selection.DoesNotExist, nil)
selector := activeAndCompatible.Add(*hasVersionPrefix, *doesNotHaveLabel)
if err := r.Client.List(ctx, tkrList, client.MatchingLabelsSelector{Selector: selector}); err != nil {
return nil, errors.Wrapf(err, "error listing TKRs with version prefix '%s'", versionPrefix)
}
var result []string
for i := range tkrList.Items {
tkr := &tkrList.Items[i]
tkrVersion, _ := version.ParseSemantic(tkr.Spec.Version)
if currentTKRVersion.LessThan(tkrVersion) {
result = append(result, tkr.Spec.Version)
}
}
sort.Slice(result, func(i, j int) bool {
vi, _ := version.ParseSemantic(result[i])
vj, _ := version.ParseSemantic(result[j])
return vi.LessThan(vj)
})
return result, nil
}

func resolvedTKRVersions(currentTKRVersion *version.Version, tkrResult data.Result) ([]string, error) {
var result []string
for _, tkrs := range tkrResult.ControlPlane.TKRsByK8sVersion {
for _, tkr := range tkrs {
Expand All @@ -218,6 +247,12 @@ func resolvedTKRVersions(currentTKR *runv1.TanzuKubernetesRelease, tkrResult dat
return result, nil
}

func vLabelMinor(major, minor uint) string {
return fmt.Sprintf("v%v.%v", major, minor)
func setUpdatesAvailable(cluster *clusterv1.Cluster, updates []string) {
if len(updates) == 0 {
conditions.MarkFalse(cluster, runv1.ConditionUpdatesAvailable, runv1.ReasonAlreadyUpToDate, clusterv1.ConditionSeverityInfo, "")
return
}
updatesAvailableCondition := conditions.TrueCondition(runv1.ConditionUpdatesAvailable)
updatesAvailableCondition.Message = fmt.Sprintf("%v", updates)
conditions.Set(cluster, updatesAvailableCondition)
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,55 +112,48 @@ var _ = Describe("clusterstatus.Reconciler", func() {
const uniqueRefField = "no-other-osimage-has-this"
BeforeEach(func() {
tkr = testdata.ChooseTKR(tkrs)
osImage = osImages[tkr.Spec.OSImages[rand.Intn(len(tkr.Spec.OSImages))].Name]

conditions.MarkTrue(tkr, runv1.ConditionCompatible)
conditions.MarkTrue(tkr, runv1.ConditionValid)
osImage.Spec.Image.Ref[uniqueRefField] = true

osImageSelector = labels.Set(osImage.Labels).AsSelector()
osImageSelectorStr = osImageSelector.String()

cluster.Spec.Topology = &clusterv1.Topology{}
cluster.Spec.Topology.Class = clusterClass.Name
cluster.Spec.Topology.Version = tkr.Spec.Kubernetes.Version
getMap(&cluster.Spec.Topology.ControlPlane.Metadata.Annotations)[runv1.AnnotationResolveOSImage] = osImageSelectorStr
})

When("a Cluster refers to a TKR", func() {
BeforeEach(func() {
osImage = osImages[tkr.Spec.OSImages[rand.Intn(len(tkr.Spec.OSImages))].Name]

conditions.MarkTrue(tkr, runv1.ConditionCompatible)
conditions.MarkTrue(tkr, runv1.ConditionValid)
osImage.Spec.Image.Ref[uniqueRefField] = true

osImageSelector = labels.Set(osImage.Labels).AsSelector()
osImageSelectorStr = osImageSelector.String()

cluster.Spec.Topology = &clusterv1.Topology{}
cluster.Spec.Topology.Class = clusterClass.Name
cluster.Spec.Topology.Version = tkr.Spec.Kubernetes.Version
getMap(&cluster.Spec.Topology.ControlPlane.Metadata.Annotations)[runv1.AnnotationResolveOSImage] = osImageSelectorStr

getMap(&cluster.Labels)[runv1.LabelTKR] = tkr.Name
})

repeat(100, func() {
repeat(10, func() {
It("should set UpdatesAvailable condition", func() {
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: util.ObjectKey(cluster)})
Expect(err).ToNot(HaveOccurred())

currentVersion, err := version.ParseSemantic(tkr.Spec.Version)
checkUpdatesAvailable(tkr, cluster)
})
})
})

When("a legacy-style Cluster refers to a TKR", func() {
BeforeEach(func() {
getMap(&cluster.Labels)[LegacyClusterTKRLabel] = tkr.Name
})

repeat(10, func() {
It("should set UpdatesAvailable condition", func() {
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: util.ObjectKey(cluster)})
Expect(err).ToNot(HaveOccurred())

cluster1 := &clusterv1.Cluster{}
Expect(r.Client.Get(context.Background(), util.ObjectKey(cluster), cluster1)).To(Succeed())

uaCond := conditions.Get(cluster1, runv1.ConditionUpdatesAvailable)
Expect(uaCond).ToNot(BeNil())
Expect(uaCond.Status).ToNot(Equal(corev1.ConditionUnknown))
switch uaCond.Status {
case corev1.ConditionTrue:
Expect(uaCond.Message).To(HavePrefix("["))
Expect(uaCond.Message).To(HaveSuffix("]"))
updateVersions := strings.Split(uaCond.Message[1:len(uaCond.Message)-1], " ")
Expect(updateVersions).ToNot(BeEmpty())
for _, updateVersionStr := range updateVersions {
updateVersion, err := version.ParseSemantic(updateVersionStr)
Expect(err).ToNot(HaveOccurred())
Expect(currentVersion.LessThan(updateVersion)).To(BeTrue(), "currentVersion '%s' should be less than updateVersion '%s'", currentVersion, updateVersion)
Expect(r.TKRResolver.Get(tkrName(updateVersionStr), &runv1.TanzuKubernetesRelease{})).ToNot(BeNil(), "TKR version: '%s'", updateVersionStr)
}
case corev1.ConditionFalse:
Expect(uaCond.Message).To(BeEmpty())
}
checkUpdatesAvailable(tkr, cluster)
})
})
})
Expand All @@ -170,6 +163,38 @@ var _ = Describe("clusterstatus.Reconciler", func() {

})

func checkUpdatesAvailable(tkr *runv1.TanzuKubernetesRelease, cluster *clusterv1.Cluster) {
currentVersion, err := version.ParseSemantic(tkr.Spec.Version)
Expect(err).ToNot(HaveOccurred())

cluster1 := &clusterv1.Cluster{}
Expect(r.Client.Get(context.Background(), util.ObjectKey(cluster), cluster1)).To(Succeed())

uaCond := conditions.Get(cluster1, runv1.ConditionUpdatesAvailable)
Expect(uaCond).ToNot(BeNil())
Expect(uaCond.Status).ToNot(Equal(corev1.ConditionUnknown))
switch uaCond.Status {
case corev1.ConditionTrue:
Expect(uaCond.Message).To(HavePrefix("["))
Expect(uaCond.Message).To(HaveSuffix("]"))
updateVersions := strings.Split(uaCond.Message[1:len(uaCond.Message)-1], " ")
Expect(updateVersions).ToNot(BeEmpty())
lastUpdateVersion := currentVersion
for _, updateVersionStr := range updateVersions {
updateVersion, err := version.ParseSemantic(updateVersionStr)
Expect(err).ToNot(HaveOccurred())
Expect(currentVersion.LessThan(updateVersion)).To(BeTrue(), "currentVersion '%s' should be less than updateVersion '%s'", currentVersion, updateVersion)
Expect(updateVersion.Minor()).To(BeNumerically(">=", currentVersion.Minor()))
Expect(updateVersion.Minor() - currentVersion.Minor()).To(BeNumerically("<=", 1))
Expect(r.TKRResolver.Get(tkrName(updateVersionStr), &runv1.TanzuKubernetesRelease{})).ToNot(BeNil(), "TKR version: '%s'", updateVersionStr)
Expect(lastUpdateVersion.LessThan(updateVersion)).To(BeTrue(), "each next updateVersion '%s' should be greater than the previous '%s'", updateVersion, lastUpdateVersion)
lastUpdateVersion = updateVersion
}
case corev1.ConditionFalse:
Expect(uaCond.Message).To(BeEmpty())
}
}

func tkrName(v string) string {
return strings.ReplaceAll(v, "+", "---")
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/v2/tkr/util/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Version struct {
buildMetadata BuildMetadata
}

func (v Version) String() string {
func (v *Version) String() string {
return "v" + v.version.String()
}

Expand Down Expand Up @@ -150,6 +150,13 @@ func Label(v string) string {
return "v" + plusReplacer.Replace(strings.TrimPrefix(v, "v"))
}

var dashReplacer = strings.NewReplacer("---", "+")

// FromLabel converts label name to version string in SemVer format.
func FromLabel(label string) string {
return "v" + dashReplacer.Replace(strings.TrimPrefix(label, "v"))
}

// WithV makes sure 'v' is prepended to the version string.
func WithV(s string) string {
if strings.HasPrefix(s, "v") {
Expand Down

0 comments on commit e9cd07f

Please sign in to comment.