Skip to content

Commit

Permalink
Flag no longer in sync ManagedOSVersions (#750)
Browse files Browse the repository at this point in the history
* Flag no longer in sync ManagedOSVersions

Signed-off-by: Andrea Mazzotti <andrea.mazzotti@suse.com>
  • Loading branch information
anmazzotti authored Jun 12, 2024
1 parent bc2f5cf commit abbc0ea
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 6 deletions.
9 changes: 8 additions & 1 deletion api/v1beta1/common_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@ const (
// ElementalManagedLabel label used to put on resources managed by the elemental operator.
ElementalManagedLabel = "elemental.cattle.io/managed"

// ElementalManagedLabel label used to put on resources managed by the elemental operator.
// ElementalManagedOSVersionChannelLabel is used to filter a set of ManagedOSVersions given the channel they originate from.
ElementalManagedOSVersionChannelLabel = "elemental.cattle.io/channel"

// ElementalManagedOSVersionChannelLastSyncAnnotation reports when a ManagedOSVersion was last synced from a channel.
ElementalManagedOSVersionChannelLastSyncAnnotation = "elemental.cattle.io/channel-last-sync"

// ElementalManagedOSVersionNoLongerSyncedAnnotation is used to mark a no longer in sync ManagedOSVersion, this highlight it can be deleted.
ElementalManagedOSVersionNoLongerSyncedAnnotation = "elemental.cattle.io/channel-no-longer-in-sync"
ElementalManagedOSVersionNoLongerSyncedValue = "true"

// SASecretSuffix is the suffix used to name registration service account's token secret
SASecretSuffix = "-token"

Expand Down
30 changes: 26 additions & 4 deletions controllers/managedosversionchannel_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ func (r *ManagedOSVersionChannelReconciler) handleSyncPod(ctx context.Context, p
if err != nil {
return ctrl.Result{}, r.handleFailedSync(ctx, pod, ch, err)
}
err = r.createManagedOSVersions(ctx, ch, data)
now := metav1.Now()
err = r.createManagedOSVersions(ctx, ch, data, now.Format(time.RFC3339))
if err != nil {
return ctrl.Result{}, r.handleFailedSync(ctx, pod, ch, err)
}
Expand All @@ -265,7 +266,6 @@ func (r *ManagedOSVersionChannelReconciler) handleSyncPod(ctx context.Context, p
Message: "successfully loaded channel data",
})
ch.Status.FailedSynchronizationAttempts = 0
now := metav1.Now()
ch.Status.LastSyncedTime = &now
return ctrl.Result{RequeueAfter: interval}, nil
default:
Expand Down Expand Up @@ -294,7 +294,7 @@ func (r *ManagedOSVersionChannelReconciler) handleFailedSync(ctx context.Context
}

// createManagedOSVersions unmarshals managedOSVersions from a byte array and creates them.
func (r *ManagedOSVersionChannelReconciler) createManagedOSVersions(ctx context.Context, ch *elementalv1.ManagedOSVersionChannel, data []byte) error {
func (r *ManagedOSVersionChannelReconciler) createManagedOSVersions(ctx context.Context, ch *elementalv1.ManagedOSVersionChannel, data []byte, syncTimestamp string) error {
logger := ctrl.LoggerFrom(ctx)

vers := []elementalv1.ManagedOSVersion{}
Expand Down Expand Up @@ -326,6 +326,9 @@ func (r *ManagedOSVersionChannelReconciler) createManagedOSVersions(ctx context.
vcpy.ObjectMeta.Labels = map[string]string{
elementalv1.ElementalManagedOSVersionChannelLabel: ch.Name,
}
vcpy.ObjectMeta.Annotations = map[string]string{
elementalv1.ElementalManagedOSVersionChannelLastSyncAnnotation: syncTimestamp,
}

if ch.Spec.UpgradeContainer != nil {
vcpy.Spec.UpgradeContainer = ch.Spec.UpgradeContainer
Expand All @@ -334,6 +337,8 @@ func (r *ManagedOSVersionChannelReconciler) createManagedOSVersions(ctx context.
if cv, ok := curVersions[v.Name]; ok {
patchBase := client.MergeFrom(cv.DeepCopy())
cv.Spec = vcpy.Spec
cv.ObjectMeta.Labels = vcpy.ObjectMeta.Labels
cv.ObjectMeta.Annotations = vcpy.ObjectMeta.Annotations
err = r.Patch(ctx, cv, patchBase)
if err != nil {
logger.Error(err, "failed to patch a managedosversion", "name", cv.Name)
Expand All @@ -353,7 +358,24 @@ func (r *ManagedOSVersionChannelReconciler) createManagedOSVersions(ctx context.
}
}

return errorutils.NewAggregate(errs)
if len(errs) > 0 {
return errorutils.NewAggregate(errs)
}

// Flagging orphan versions
for _, version := range curVersions {
if lastSyncTime, found := version.Annotations[elementalv1.ElementalManagedOSVersionChannelLastSyncAnnotation]; !found || (lastSyncTime != syncTimestamp) {
logger.Info("ManagedOSVersion no longer synced through this channel", "name", version.Name)
patchBase := client.MergeFrom(version.DeepCopy())
version.ObjectMeta.Annotations[elementalv1.ElementalManagedOSVersionNoLongerSyncedAnnotation] = elementalv1.ElementalManagedOSVersionNoLongerSyncedValue
if err := r.Patch(ctx, version, patchBase); err != nil {
logger.Error(err, "Could not patch ManagedOSVersion as no longer in sync", "name", version.Name)
return fmt.Errorf("deprecating ManagedOSVersion '%s': %w", version.Name, err)
}
}
}

return nil
}

// getAllOwnedManagedOSVersions returns a map of all ManagedOSVersions labeled with the given channel, resource name is used as the map key
Expand Down
100 changes: 99 additions & 1 deletion controllers/managedosversionchannel_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,22 @@ const updatedJSON = `[
}
]`

// v0.1.0 removed
const deprecatingJSON = `[
{
"metadata": {
"name": "v0.2.0"
},
"spec": {
"version": "v0.2.0",
"type": "container",
"metadata": {
"upgradeImage": "foo/bar:v0.2.0"
}
}
}
]`

const invalidJSON = `[
{
"metadata": {
Expand Down Expand Up @@ -494,7 +510,13 @@ var _ = Describe("managed os version channel controller integration tests", func
if mgrCancel != nil {
mgrCancel()
}
Expect(test.CleanupAndWait(ctx, cl, ch, pod, managedOSVersion)).To(Succeed())
Expect(test.CleanupAndWait(ctx, cl, ch, pod)).To(Succeed())

list := &elementalv1.ManagedOSVersionList{}
Expect(cl.List(ctx, list)).To(Succeed())
for _, version := range list.Items {
Expect(test.CleanupAndWait(ctx, cl, &version)).To(Succeed())
}
})

It("should reconcile and sync managed os version channel object and apply channel updates", func() {
Expand Down Expand Up @@ -570,6 +592,82 @@ var _ = Describe("managed os version channel controller integration tests", func
Expect(managedOSVersion.Spec.Version).To(Equal("v0.1.0-patched"))
})

It("should deprecate a version after it's removed from channel", func() {
ch.Spec.Type = "json"

Expect(cl.Create(ctx, ch)).To(Succeed())

// Pod is created
Eventually(func() bool {
err := cl.Get(ctx, client.ObjectKey{
Name: ch.Name,
Namespace: ch.Namespace,
}, pod)
return err == nil
}, 12*time.Second, 2*time.Second).Should(BeTrue())
setPodPhase(pod, corev1.PodSucceeded)

Eventually(func() bool {
err := cl.Get(ctx, client.ObjectKey{
Name: ch.Name,
Namespace: ch.Namespace,
}, ch)
return err == nil && ch.Status.Conditions[0].Status == metav1.ConditionTrue
}, 12*time.Second, 2*time.Second).Should(BeTrue())

Expect(cl.Get(ctx, client.ObjectKey{
Name: "v0.1.0",
Namespace: ch.Namespace,
}, managedOSVersion)).To(Succeed())
Expect(managedOSVersion.Spec.Version).To(Equal("v0.1.0"))

// Pod is deleted
Eventually(func() bool {
err := cl.Get(ctx, client.ObjectKey{
Name: ch.Name,
Namespace: ch.Namespace,
}, pod)
return err != nil && apierrors.IsNotFound(err)
}, 12*time.Second, 2*time.Second).Should(BeTrue())

// Simulate a channel content change
syncerProvider.SetJSON(deprecatingJSON)

// Updating the channel after the minimum time between syncs causes an automatic update
patchBase := client.MergeFrom(ch.DeepCopy())
ch.Spec.SyncInterval = "10m"
Expect(cl.Patch(ctx, ch, patchBase)).To(Succeed())

// Pod is created
Eventually(func() bool {
err := cl.Get(ctx, client.ObjectKey{
Name: ch.Name,
Namespace: ch.Namespace,
}, pod)
return err == nil
}, 12*time.Second, 2*time.Second).Should(BeTrue())
setPodPhase(pod, corev1.PodSucceeded)

// New added versions are synced
Eventually(func() bool {
err := cl.Get(ctx, client.ObjectKey{
Name: "v0.2.0",
Namespace: ch.Namespace,
}, managedOSVersion)
return err == nil
}, 12*time.Second, 2*time.Second).Should(BeTrue())
_, found := managedOSVersion.Annotations[elementalv1.ElementalManagedOSVersionNoLongerSyncedAnnotation]
Expect(found).To(BeFalse(), "no-longer-synced annotation must not be present when versions are actually synced")
Expect(managedOSVersion.Annotations[elementalv1.ElementalManagedOSVersionChannelLastSyncAnnotation]).ToNot(BeEmpty(), "Last sync annotation should contain the UTC timestamp")

// After channel update already existing versions were patched
Expect(cl.Get(ctx, client.ObjectKey{
Name: "v0.1.0",
Namespace: ch.Namespace,
}, managedOSVersion)).To(Succeed())
Expect(managedOSVersion.Annotations[elementalv1.ElementalManagedOSVersionNoLongerSyncedAnnotation]).To(Equal(elementalv1.ElementalManagedOSVersionNoLongerSyncedValue))
})

It("should not reconcile again if it errors during pod lifecycle", func() {
ch.Spec.Type = "json"

Expand Down

0 comments on commit abbc0ea

Please sign in to comment.