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

Change operation type to update for services #224

Conversation

alegacy
Copy link
Contributor

@alegacy alegacy commented Oct 1, 2024

The fact that we were using a "patch" operation on services is leading to some errors in the reconcile loop. The "patch" should be reserved to use cases where we read an object from the system, make some changes to our local copy, and then want to "patch" it using the API to only send those fields that have changed. What we were doing was creating a brand new object and attempting to "patch" that against what the running system already had which lead to trying to delete several runtime only attributes which I believe is what was leading to the errors.

The fact that we were using a "patch" operation on services is leading
to some errors in the reconcile loop.  The "patch" should be reserved
to use cases where we read an object from the system, make some changes
to our local copy, and then want to "patch" it using the API to only
send those fields that have changed.  What we were doing was creating
a brand new object and attempting to "patch" that against what the
running system already had which lead to trying to delete several
runtime only attributes which I believe is what was leading to the
errors.

Signed-off-by: Allain Legacy <[email protected]>
@@ -896,7 +896,7 @@ func (t *reconcilerTask) createService(ctx context.Context, resourceName string)
}

t.logger.InfoContext(ctx, "[createService] Create/Update/Patch Service: ", "name", resourceName)
if err := utils.CreateK8sCR(ctx, t.client, newService, t.object, utils.PATCH); err != nil {
if err := utils.CreateK8sCR(ctx, t.client, newService, t.object, utils.UPDATE); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Off topic/general comment...CreateK8sCR seems to do way too many things right? One func to do create, update, patch, taking ownership, doing all sorts of reflection...

Copy link
Collaborator

Choose a reason for hiding this comment

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

The purpose was to create and update objects in the same function so that the main reconciliation loop is cleaner. When you create an object, it's when you also specify its ownership, so these should be together anyway.
The inventory controller makes sure the servers exist at all times (so it recreates them if they've been deleted) and that they look as specified in the Inventory CR spec (so it updates/patches if someone edits them directly).
I suppose we could separate the updates from creation in different functions.

What enhancement did you have in mind for this?

Copy link
Collaborator

@pixelsoccupied pixelsoccupied Oct 1, 2024

Choose a reason for hiding this comment

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

Few suggestions:

  • We cant say "Create" and then take an operation parameter (which happens to an arbitrary string)
  • This is not simply "Create", violating single responsibility principle. Instead we break it down into updateOrPatchObject createObject etc etc. And then finally the caller should explicitly check for errors as needed e.g. k8srr.IsAlreadyExists(err) for create
  • Given we already have access all the generated code for all CRUD reqs....we should try to use them directly instead of figure out types at runtime with Object . i.e c.Create(ctx, job) (job being a struct type job). This means more code but idiomatic/in-line with Go

wdyt?

Copy link
Contributor Author

@alegacy alegacy Oct 1, 2024

Choose a reason for hiding this comment

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

What's going to happen if we split it into a updateOrPatchObject and a createObject is that someone will get tired of seeing that pattern spread throughout the code and will create a wrapper for that flow called createOrUpdateObject and we're more or less back to this starting point.

I do agree that the name could be improved upon, and certainly how we use it will need to be addressed in the future because as of now the 'update' is rather unsophisticated and doesn't handle needing to modify objects very gracefully at all. They all end up being just a bulk replace of the object which is likely causing issues to anyone doing a watch on them (potentially)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree that renaming it to smth like ensureK8sCR would be a better description of the functionality.
I don't really agree that this is violating the SRP principle, especially with the renaming and possibly enhancing the update.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah definitely doing createObject would ultimate be wrapped up. Which is why it's better to be explicit (the third point) and pass the exact typed...so instead we should have createJob or createDeployment instead of super generic funcs

@Missxiaoguo
Copy link
Collaborator

What we were doing was creating a brand new object and attempting to "patch" that against what the running system already had which lead to trying to delete several runtime only attributes

So this is really an update, not a patch, but from a result perspecitive, I think both lead to the same outcome since update completely replaces the object running on the system.

@alegacy
Copy link
Contributor Author

alegacy commented Oct 1, 2024

@Missxiaoguo The issue is that patch should not be used in this way. It was intended to be used like this:

old := get("some object")
new := old.clone()
new.someField = "new value"
if err := utils.CreateK8sCR(ctx, t.client, new, old, utils.PATCH); err != nil {

because it will try to do a diff between the old and new objects and only replace the fields that have been modified. BUT... if you do a diff between a statically created object and the current runtime version of an object the diff will contain many fields (some of which are critical) and so doing a patch on it will try to remove those fields which in turn leads to errors. If you look at the other usages of PATCH in our code they are done properly. This one for some reason was accidentally set to PATCH instead of UPDATE.

@Missxiaoguo
Copy link
Collaborator

@Missxiaoguo The issue is that patch should not be used in this way. It was intended to be used like this:

old := get("some object")
new := old.clone()
new.someField = "new value"
if err := utils.CreateK8sCR(ctx, t.client, new, old, utils.PATCH); err != nil {

because it will try to do a diff between the old and new objects and only replace the fields that have been modified. BUT... if you do a diff between a statically created object and the current runtime version of an object the diff will contain many fields (some of which are critical) and so doing a patch on it will try to remove those fields which in turn leads to errors. If you look at the other usages of PATCH in our code they are done properly. This one for some reason was accidentally set to PATCH instead of UPDATE.

Yes, I understand. Anyhow, we should switch to Update since it’s not supposed to be used that way. I just wanted to point out that with Update, the fields from the runtime version that aren’t present in the new object will also be removed as it's a replace. So, in this case, the incorrect use of Patch leads to the same result but yes, we shouldn't use Patch here.

@donpenney
Copy link
Collaborator

/lgtm

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Oct 2, 2024
@browsell
Copy link
Collaborator

browsell commented Oct 2, 2024

/approve

Copy link

openshift-ci bot commented Oct 2, 2024

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: browsell

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:

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

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Oct 2, 2024
@openshift-merge-bot openshift-merge-bot bot merged commit 89ca4d0 into openshift-kni:main Oct 2, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants