Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v1.6.x backport] Flag no longer in sync ManagedOSVersions #752

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading