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

Update reference resolution interface types #121

Merged

Conversation

RedbackThomson
Copy link
Contributor

Description of changes:
Creates a new ReferenceManager type that re-defines ResolveReferences and adds a new ClearResolvedReferences methods to ResourceManager. ResolveReferences now returns a copy of the original AWSResource. ClearResolvedReferences also returns a copy of the original AWSResource.

The reconciler now resolves the references once at the beginning and passes around the resolved versions of the resource. Right before the resource is patched, in patchMetadataAndSpec, the resource is cleaned of references so that they are not persisted to etcd.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link
Member

@a-hilaly a-hilaly left a comment

Choose a reason for hiding this comment

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

I understand the need for those changes for now and overall it makes sense!
Kinda a low level review from my end. I mostly have questions but most importantly i want to bring this:
One think that is crossing my mind now: Should we NOT allow users to have a mix between referenced and non-referenced data for the same field?

pkg/condition/condition.go Outdated Show resolved Hide resolved
Comment on lines +25 to +32
// ResolveReferences finds if there are any Reference field(s) present
// inside AWSResource passed in the parameter and attempts to resolve those
// reference field(s) into their respective target field(s). It returns a
// copy of the input AWSResource with resolved reference(s), a boolean which
// is set to true if the resource contains any references (regardless of if
// they are resolved successfully) and an error if the passed AWSResource's
// reference field(s) could not be resolved.
ResolveReferences(context.Context, client.Reader, AWSResource) (AWSResource, bool, error)
Copy link
Member

Choose a reason for hiding this comment

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

Why is it important to know whether a resource has references or not? Couldn't we just rely on err == nil || err != nil ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are 3 cases:

  1. References exist, and resolution was successful
  2. References exist, but resolution was not successful
  3. References don't exist

err only covers cases 1 and 2. Case 3 doesn't execute anything, so it'll never return an err. The only way we know if references exist, then, is to return a bool to say we at least attempted to resolve them.

@@ -811,6 +808,7 @@ func (r *resourceReconciler) setResourceManaged(
// allows the CR to be deleted by the Kubernetes API server.
func (r *resourceReconciler) setResourceUnmanaged(
ctx context.Context,
rm acktypes.AWSResourceManager,
Copy link
Member

Choose a reason for hiding this comment

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

Shall we just make rm part of resourceReconciler instead of passing it around between functions?

// is set to true if the resource contains any references (regardless of if
// they are resolved successfully) and an error if the passed AWSResource's
// reference field(s) could not be resolved.
ResolveReferences(context.Context, client.Reader, AWSResource) (AWSResource, bool, error)
Copy link
Member

Choose a reason for hiding this comment

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

Same here maybe make client.Reader part of the implementation?

Copy link
Collaborator

@jaypipes jaypipes left a comment

Choose a reason for hiding this comment

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

Still thinking this through but I think these changes make sense. :+1

Comment on lines +624 to +629
// NOTE(redbackthomson): This method returns an updated version of the latest
// parameter. This version has an updated metadata.resourceVersion, which is
// incremented in the process of calling Patch. This is intentional, because
// without updating the resource's metadata.resourceVersion, the resource cannot
// be passed to Patch again later in the reconciliation if Patch is called with
// the Optimistic Locking option.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps update this note to say that the returned resource has its resource references removed?

patch := client.MergeFrom(dobj)
err = r.kc.Patch(ctx, latest.RuntimeObject(), patch)
lorig := latestCleaned.DeepCopy()
patch := client.MergeFrom(desiredCleaned.RuntimeObject())
Copy link

Choose a reason for hiding this comment

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

noting that this is now effectively

client.MergeFrom(rm.ClearResolvedReferences(desired).RuntimeObject())

vs previously

client.MergeFrom(desired.DeepCopy().RuntimeObject())

I'm not 100% positive from reading the corresponding code-generator change -- but I think that ClearResolvedReferences does not return a deep copy of the resource, so you're effectively doing away with the deep copy part here. Does that matter? Is it intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made sure that all of the reference resolution-related methods returned copies. You can see this in the ClearResolvedReferences method - https://gist.github.com/RedbackThomson/8e11cbbe96065a4eb812c387665c747d#file-references-go-L34

Copy link
Member

@a-hilaly a-hilaly left a comment

Choose a reason for hiding this comment

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

👍

@jljaco
Copy link

jljaco commented Apr 13, 2023

/lgtm

@ack-prow ack-prow bot added the lgtm Indicates that a PR is ready to be merged. label Apr 13, 2023
@ack-prow
Copy link

ack-prow bot commented Apr 13, 2023

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: A-Hilaly, jaypipes, jljaco, RedbackThomson

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 [A-Hilaly,RedbackThomson,jaypipes,jljaco]

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

@ack-prow ack-prow bot merged commit af66d1b into aws-controllers-k8s:main Apr 13, 2023
@jljaco jljaco removed their assignment Apr 13, 2023
@RedbackThomson RedbackThomson deleted the upgrade-resolve-references branch April 13, 2023 16:02
ack-prow bot pushed a commit to aws-controllers-k8s/code-generator that referenced this pull request Apr 17, 2023
#435)

Implements aws-controllers-k8s/runtime#121

Description of changes:
This PR abstracts out the process of iterating through all references into a separate method called `iterReferenceValues`. `iterReferenceValues` produces Go code that drills down into the spec for every ref, iterating through any slices as necessary. Once it reaches the ref object, it calls a callback which can be used by other methods for logic pertaining to accessing the refs. 

This change allows `ResolveReferencesForField` to access all refs within lists of structs (or even within lists of lists of structs, etc.). `iterReferenceValues` is also used to implement `ClearResolvedReferencesForField`, which simply sets the concrete value to `nil` if it detects a non-nil value in the ref field (or `len > 0` for lists of refs). 

This PR also removes `hasNonNilReferences`. `hasNonNilReferences` was being used to indicate whether there were any references inside the resource, which required another set of iterating through all ref fields. Instead, each `resolveReferencesFor*` returns a boolean which indicates whether it found a reference during its iteration.

Below is a Gist that shows the output of running the code-generator on the current EC2 `generator.yaml`:
https://gist.github.com/RedbackThomson/8e11cbbe96065a4eb812c387665c747d

For testing:
`ec2` and `lambda` already have a series of tests for references. Both of these tests are passing, but they were also passing before this PR. I have a branch of the `ec2-controller` with additional tests using `RouteTables`, which have lists of structs containing refs, and all of those are passing as well. 

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved lgtm Indicates that a PR is ready to be merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

List of structs containing references are patched without references
4 participants