Support for having circular dependencies while using referencers#328
Support for having circular dependencies while using referencers#328muvaf merged 7 commits intocrossplane:masterfrom
Conversation
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
054d4e0 to
ec82fef
Compare
| ) | ||
|
|
||
| // A Reference to a named object. | ||
| type Reference struct { |
There was a problem hiding this comment.
The policies here affect ref -> value transition but there is also selector -> ref where similar policies could apply. But as far as I can tell, both issues are about ref -> value, so having the policies only here could make sense for now until users see a need to customize selector -> ref as well.
There was a problem hiding this comment.
Doesn't for example #250 (comment) pertain to selectors? I could imagine the race @chlunde describes being a problem here, i.e.:
- Foo A is created.
- fooSelector selects Foo A and creates a reference.
- Foo B is created
- fooSelector would have selected Foo B, but doesn't because it already successfully selected its references.
There was a problem hiding this comment.
Thank you for bringing up this, it was a point that I thought while working on these issues. Implemented supports/API for the Reference field can also be provided for Selector field, as you said. However, at this point, I think a new discussion can start.
Let me share my thoughts via an example:
..
forProvider:
vpcIdSelector:
matchLabels:
key: value
..As you know, while we using the Selector field for resolution, we are filling the Reference field. So after we selected and resolved the reference by using the Selector, new state of the resource will be like that:
..
forProvider:
vpcId: vpc-1111111111
vpcIdRef:
name: sample-vpc
vpcIdSelector:
matchLabels:
key: value
..Now, when we review the function (this is Resolve function), we are observing that, firstly the Reference field is checked and then if it is not empty, the resolution process is done by using the Reference field and we return early. In other words, if the Reference field is set, we do not process the Selector field. Therefore, after this point, even if I update the Selector field, it will have no effect. Because the Reference field is full and only that field will be processed in the next loops.
I think this is an independent point from the resolution/resolve policies. Because, at this point, issues such as which resolution method is more dominant and which one to choose for situations where both are set will come to the fore. Let me share an example to clarify my point. Imagine that a user tries to provision an external resource by using the following manifest:
..
forProvider:
vpcIdRef:
name: sample-vpc
vpcIdSelector:
matchLabels:
key: value
..For a resource that is being create by this manifest, we can say that the reference in the Reference field is dominant. So, the Selector field will not be used for resolution process. In fact, this is the same situation we encountered after selecting references and filling Reference field via Selector. And from this point on, the Selector field will not be considered again. Therefore, changes on this field, such as updating list or changing the policy, will not be affective.
As a result, I think providing support of new Policy API for Selector field makes sense. However, if I do not miss any point, it seems that we will have to do this with a solution of the point I tried to explain above.
There was a problem hiding this comment.
@negz Yes, you're right. We need to address the selector as well.
As a result, I think providing support of new Policy API for Selector field makes sense. However, if I do not miss any point, it seems that we will have to do this with a solution of the point I tried to explain above.
@sergenyalcin If I understood you correctly, you're saying that the fact that we don't take any action after the ref is found will be changed. I think that's fine since it's configured by the user.
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
|
About my comment, I want to propose a few solutions. Firstly, let me summarize my point again. When we set a Selector, we are selecting reference resources and putting them to the Reference field. In our Resolve functions, the Reference field is checking firstly, and after resolving references by using this Reference field we return early. This means that, Reference field is used as cache of Selector. So this blocks 'resolving always scenario' for Selector field (editing Selector and expecting some changes). Some proposed solutions:
To be honest, I don't have a strong feeling about which of these would be the better approach. What are your thoughts @negz about this point? Do you have any other approach for addressing this? cc @muvaf |
I think this option is more intuitive from the perspective of users since the main job of selector is to select a reference and it makes it straight-forward to understand. If we add policy field to the selector as well, similar to reference, I think users could configure it while being aware of the risk that |
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
@muvaf I added the new API for Selector and also changed the order of Reference and Selector fields. Now, we are resolving the Selector field firstly. |
| if err := r.client.Get(ctx, types.NamespacedName{Name: req.Reference.Name}, req.To.Managed); err != nil { | ||
| return ResolutionResponse{}, errors.Wrap(err, errGetManaged) | ||
| // The selector is set - resolve it. | ||
| if req.Selector != nil { |
There was a problem hiding this comment.
This results in behavior change for cases where both ref and selector are given and no policy is set. Chatted offline about possible solutions.
There was a problem hiding this comment.
As we discussed, I preserved the order (first Reference field then Selector field).
For new Always policy scenario, I implemented a possible solution (we discussed offline) that sets the Reference field to nil when Selector is not empty and resolve policy is always.
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
|
About this PR, I want ot share some points. We are trying to support new capabilities for the reference resoultion by providing policies. First one is the resolving references for every reconciliation loop (Always policy), the another one is that resolving references as Optional. We have some challenges about them, especially Always one. I think, the existing API is very appropriate for the current behaviors:
Although this API works very successful for many cases, it seems that, for our new cases that we want support it has some weak parts. All the concerns that I will talk about a little later have been expressed from different angles in the issues. My purpose here is to write about these concerns in this PR and explore whether there is a better way. I want to share two points (of course, we can talk about many difference cases). These two cases are about the new Always policy.
SubnetIDRefs:
- name: subnet-1
policy:
resolve: Always
- name: subnet-2
- name: subnet-3
- name: subnet-4
policy:
resolve: AlwaysAs you can see, some inconsistent situations may occur when the new API is used. For now, when we see an Always policy for an element of list, we are resolving all references for every reconcile loop. In other words, it is sufficient for only one of the elements in the list have an Always policy for resolution in each loop. This might seem intuitively reasonable. However, it is theoretically possible to implement the opposite of this behavior. So it's actually a preference, and it might be reasonable to implement a behavior otherwise.
vpcIdSelector:
matchLabels:
key: value
policy:
resolve: AlwaysAfter first reconciliation loop: vpcIdReference:
- name: vpc-example
vpcIdSelector:
matchLabels:
key: value
policy:
resolve: AlwaysThen user edited the Reference field value by using kubectl: vpcIdReference:
- name: MANUALLY-CHANGED-vpc-example
vpcIdSelector:
matchLabels:
key: value
policy:
resolve: AlwaysIn next reconciliation loop, because of the Always policy, selector will resolve the reference again and override the changed desired state of this resource: vpcIdReference:
- name: vpc-example
vpcIdSelector:
matchLabels:
key: value
policy:
resolve: AlwaysFor the first state of the resource, the Reference field was empty so filling this via controller is the late-initialization concept. However, after changed this value manually, this means that the user gives a new desired state. So I am not sure that we must override this. So, for now, the new Policies are make sense and resolve the issues. Also I think this is an improvement for the reference resolution pipeline. However, we may discuss re-designing this API for providing better concept for our new cases. |
muvaf
left a comment
There was a problem hiding this comment.
LGTM! Thank you @sergenyalcin for driving such a core change! I have some nits but it's good to go, let me know when it's ready to merge.
| kerrors "k8s.io/apimachinery/pkg/api/errors" | ||
|
|
||
| "k8s.io/apimachinery/pkg/types" |
There was a problem hiding this comment.
| kerrors "k8s.io/apimachinery/pkg/api/errors" | |
| "k8s.io/apimachinery/pkg/types" | |
| kerrors "k8s.io/apimachinery/pkg/api/errors" | |
| "k8s.io/apimachinery/pkg/types" |
goimports
| isAlways := false | ||
| if rr.Selector != nil { | ||
| if rr.Selector.Policy.IsResolvePolicyAlways() { | ||
| rr.Reference = nil |
There was a problem hiding this comment.
Could you leave a comment here about why we set it to nil?
|
💯 |
@sergenyalcin Could you open an issue regarding this? I don't love that each element has its own policy but there isn't really a good way of adding a policy to the whole array without a breaking change. Maybe an annotation to point to the array field and say
I think this is pretty much the core use case users want with |
| // the corresponding field is not present. Use 'Always' to resolve the | ||
| // reference on every reconcile. | ||
| // +optional | ||
| Resolve *ResolvePolicy `json:"resolve,omitempty"` |
There was a problem hiding this comment.
Would it make sense for it to be possible to explicitly specify IfNotPresent? Currently that's not possible because the enum validation only allows Always.
There was a problem hiding this comment.
Yes, I think enum should include all possibilities. @sergenyalcin if there isn't a specific reason, it'd be nice to open a PR to add it before the change propagates to providers.
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
Description of your changes
Fixes #311
Fixes #250
This PR adds a new API about the reference resolution/resolve policies. The new Policy struct has two Policy types:
This Policy API was added for both Reference and Selector structs. Also, to provide the Always resolution, the order of the resolving changed. Before first Reference then Selector, now first Selector then Reference.
For details please check the following comments:
#328 (comment)
#328 (comment)
#328 (comment)
#328 (comment)
I have:
make reviewable testto ensure this PR is ready for review.How has this code been tested
For circular dependency fix, this PR was tested by provisioning the SecurityGroup resource in provider-aws (with the following manifest). This resource includes a circular dependency:
For resolved only once issue, this PR was tested by provisioning the DBSubnetGroup resource with a problematic subnet reference list (with the following manifest):
Then this resource was updated with the following subnet reference list:
After adding the Always policy, I observed that the references were resolved for every reconcile loop.