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

SANDBOX-838: loop update calls #1074

Merged
merged 6 commits into from
Dec 3, 2024

Conversation

rsoaresd
Copy link
Contributor

@rsoaresd rsoaresd commented Nov 28, 2024

Description

#1071 addressed a potential fix, but to ensure we do not face it in the future, we should do the update call in a loop.

Since other update calls are not being done in a loop, this PR addressed it as well.

Issue ticket number and link

SANDBOX-838

@rsoaresd
Copy link
Contributor Author

/retest

hitting known flaky test SANDBOX-837

@rsoaresd
Copy link
Contributor Author

/retest

hitting known flaky test SANDBOX-836

Copy link
Collaborator

@MatousJobanek MatousJobanek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for taking care of this.
I believe that we could lower the amount of copy-pasting by expanding the Waiter generic type

// Waiter is a helper struct for `wait.For()` that provides functions to query the cluster waiting
// for the results.
type Waiter[T client.Object] struct {
await *Awaitility
t *testing.T
gvk schema.GroupVersionKind
}

cc @metlos
Something like this could work:

func (w *Waiter[T]) Update(status bool, objectName string, modify func(T)) (T, error) {
	var objectToReturn T
	err := wait.PollUntilContextTimeout(context.TODO(), w.await.RetryInterval, w.await.Timeout, true, func(ctx context.Context) (done bool, err error) {
		obj := &unstructured.Unstructured{}
		obj.SetGroupVersionKind(w.gvk)
		if err := w.await.Client.Get(context.TODO(), types.NamespacedName{Namespace: w.await.Namespace, Name: objectName}, obj); err != nil {
			return true, err
		}
		object, err := w.cast(obj)
		if err != nil {
			return false, fmt.Errorf("failed to cast the object to GVK %v: %w", w.gvk, err)
		}
		modify(object)
		if status {
			// Update the Status
			if err := w.await.Client.Status().Update(context.TODO(), object); err != nil {
				w.t.Logf("error updating '%v' Status '%s': %s. Will retry again...", w.gvk, objectName, err.Error())
				return false, err
			}
		} else {
			// Update the Spec
			if err := w.await.Client.Update(context.TODO(), object); err != nil {
				w.t.Logf("Error updating '%v' Spec '%s': %s. Will retry again...", w.gvk, objectName, err.Error())
				return false, err
			}
		}
		objectToReturn = object
		return true, nil
	})
	return objectToReturn, err
}

so you could then call it:

_, err = For(t, hostAwait.Awaitility, &toolchainv1alpha1.NSTemplateTier{}).
	Update(false, tier.NSTemplateTier.Name, func(nstt *toolchainv1alpha1.NSTemplateTier) {
		nstt.Spec = tier.NSTemplateTier.Spec
	})

or:

event, err = For(t, hostAwait.Awaitility, &toolchainv1alpha1.SocialEvent{}).
	Update(false, event.Name, func(ev *toolchainv1alpha1.SocialEvent) {
		ev.Spec.SpaceTier = "base"
	})

You could also add other methods with some syntactic sugar:

func (w *Waiter[T]) Update(objectName string, modify func(T)) (T, error) {
	return w.doUpdate(false, objectName, modify)
}

func (w *Waiter[T]) UpdateStatus(objectName string, modify func(T)) (T, error) {
	return w.doUpdate(true, objectName, modify)
}

func (w *Waiter[T]) doUpdate(status bool, objectName string, modify func(T)) (T, error) {
	var objectToReturn T
...

Comment on lines 235 to 244
t.Run("use proxy to update a HAS Application CR in the user appstudio namespace via proxy API", func(t *testing.T) {
// Update application
// Get application name
applicationName := user.getApplicationName(0)
// Get application
proxyApp := user.getApplication(t, proxyCl, applicationName)
// Update DisplayName
changedDisplayName := fmt.Sprintf("Proxy test for user %s - updated application", tenantNsName(user.compliantUsername))
proxyApp.Spec.DisplayName = changedDisplayName
err := proxyCl.Update(context.TODO(), proxyApp)
// Update application
proxyApp, err := awaitilities.Host().UpdateApplication(t, proxyCl, applicationName, tenantNsName(user.compliantUsername), func(app *appstudiov1.Application) {
app.Spec.DisplayName = changedDisplayName
})
require.NoError(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but I think that we can drop the "AppStudio API"-specific test cases. It's completely fine to test modification on standard k8s resources (eg. ConfigMap), no need to maintain the other API

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from that, there is actually no Application controller running that would watch/modify these resources. This means that we don't expect any conflicts for this resource and the update through the proxy should work fine without having the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much! I will remove the loop here. And take care of the "AppStudio API"-specific test cases in a different PR

test/e2e/parallel/socialevent_test.go Outdated Show resolved Hide resolved
test/e2e/parallel/socialevent_test.go Outdated Show resolved Hide resolved
testsupport/wait/host.go Outdated Show resolved Hide resolved
@rsoaresd
Copy link
Contributor Author

Thanks a lot for taking care of this. I believe that we could lower the amount of copy-pasting by expanding the Waiter generic type

// Waiter is a helper struct for `wait.For()` that provides functions to query the cluster waiting
// for the results.
type Waiter[T client.Object] struct {
await *Awaitility
t *testing.T
gvk schema.GroupVersionKind
}

cc @metlos
Something like this could work:

func (w *Waiter[T]) Update(status bool, objectName string, modify func(T)) (T, error) {
	var objectToReturn T
	err := wait.PollUntilContextTimeout(context.TODO(), w.await.RetryInterval, w.await.Timeout, true, func(ctx context.Context) (done bool, err error) {
		obj := &unstructured.Unstructured{}
		obj.SetGroupVersionKind(w.gvk)
		if err := w.await.Client.Get(context.TODO(), types.NamespacedName{Namespace: w.await.Namespace, Name: objectName}, obj); err != nil {
			return true, err
		}
		object, err := w.cast(obj)
		if err != nil {
			return false, fmt.Errorf("failed to cast the object to GVK %v: %w", w.gvk, err)
		}
		modify(object)
		if status {
			// Update the Status
			if err := w.await.Client.Status().Update(context.TODO(), object); err != nil {
				w.t.Logf("error updating '%v' Status '%s': %s. Will retry again...", w.gvk, objectName, err.Error())
				return false, err
			}
		} else {
			// Update the Spec
			if err := w.await.Client.Update(context.TODO(), object); err != nil {
				w.t.Logf("Error updating '%v' Spec '%s': %s. Will retry again...", w.gvk, objectName, err.Error())
				return false, err
			}
		}
		objectToReturn = object
		return true, nil
	})
	return objectToReturn, err
}

so you could then call it:

_, err = For(t, hostAwait.Awaitility, &toolchainv1alpha1.NSTemplateTier{}).
	Update(false, tier.NSTemplateTier.Name, func(nstt *toolchainv1alpha1.NSTemplateTier) {
		nstt.Spec = tier.NSTemplateTier.Spec
	})

or:

event, err = For(t, hostAwait.Awaitility, &toolchainv1alpha1.SocialEvent{}).
	Update(false, event.Name, func(ev *toolchainv1alpha1.SocialEvent) {
		ev.Spec.SpaceTier = "base"
	})

You could also add other methods with some syntactic sugar:

func (w *Waiter[T]) Update(objectName string, modify func(T)) (T, error) {
	return w.doUpdate(false, objectName, modify)
}

func (w *Waiter[T]) UpdateStatus(objectName string, modify func(T)) (T, error) {
	return w.doUpdate(true, objectName, modify)
}

func (w *Waiter[T]) doUpdate(status bool, objectName string, modify func(T)) (T, error) {
	var objectToReturn T
...

@MatousJobanek excellent idea!! 🚀 Should we move the Waiter generic type to toolchain-common or remain here?

@MatousJobanek
Copy link
Collaborator

as discussed in the KubeSaw sync call, it should stay here 👍

@rsoaresd
Copy link
Contributor Author

rsoaresd commented Dec 2, 2024

as discussed in the KubeSaw sync call, it should stay here 👍

Perfect, thank you so much! I addressed for now only some cases to the pr to not be too heavy to review. I will clean up the other Update's functions in a separate PR once this one is merged

Copy link
Collaborator

@MatousJobanek MatousJobanek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks for taking care of this, and thanks for implementing the generic approach 👍

testsupport/wait/awaitility.go Outdated Show resolved Hide resolved
@openshift-ci openshift-ci bot added the approved label Dec 3, 2024
Comment on lines 916 to 922
return false, err
}
} else {
// Update the Spec
if err := w.await.Client.Update(context.TODO(), object); err != nil {
w.t.Logf("Error updating '%v' Spec '%s': %s. Will retry again...", w.gvk, objectName, err.Error())
return false, err
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that I made a mistake in my suggestion, we should not return an error here; otherwise, the poll loop immediately exists with an error:

Suggested change
return false, err
}
} else {
// Update the Spec
if err := w.await.Client.Update(context.TODO(), object); err != nil {
w.t.Logf("Error updating '%v' Spec '%s': %s. Will retry again...", w.gvk, objectName, err.Error())
return false, err
return false, nil
}
} else {
// Update the Spec
if err := w.await.Client.Update(context.TODO(), object); err != nil {
w.t.Logf("Error updating '%v' Spec '%s': %s. Will retry again...", w.gvk, objectName, err.Error())
return false, nil

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this true also for the status update faliure : https://github.com/codeready-toolchain/toolchain-e2e/pull/1074/files#diff-4abc90985c7fe1e595e0d6f5131e1f4757a6f00c9239f40ae09375d12f15e530R916

should we not return an error here as well and retry ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is addressed in line 916 of @MatousJobanek suggestion! Goinggggg!!

Locally, it passed for me. That's why I did not notice it. Sorry!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh nice, sorry I've missed that from the suggestion, never mind 👍

Copy link

sonarcloud bot commented Dec 3, 2024

Copy link
Contributor

@mfrancisc mfrancisc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great Job 👏
I like the new generic update functions 👍

I have only one minor comment/question.

test/e2e/parallel/space_test.go Show resolved Hide resolved
Copy link

openshift-ci bot commented Dec 3, 2024

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: MatousJobanek, mfrancisc, rsoaresd

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:
  • OWNERS [MatousJobanek,mfrancisc]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@rsoaresd
Copy link
Contributor Author

rsoaresd commented Dec 3, 2024

/retest

hitting known flaky test SANDBOX-837

@rsoaresd rsoaresd merged commit 91404c7 into codeready-toolchain:master Dec 3, 2024
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants