-
Notifications
You must be signed in to change notification settings - Fork 355
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
Add syncrepl (rfc-4533) consumer (persistent search) #447
Conversation
da18de8
to
91f9494
Compare
#446 will fix DATA RACE with |
91f9494
to
87ae55c
Compare
I rebased to include #446 for CI (github actions). |
@cpuschma @johnweldon I've been waiting for this feature since March 2023 (#422). I have added the syncrepl feature so that the existing process has no side effects. What do you think? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once again, thank you very much for your PR and your constant help improving the project. Overall, thinks are looking good but we need to stay focused to not derivate too much from existing patterns. I'm always open for feedback if you disagree. @go-ldap/committers any comments?
response_syncrepl.go
Outdated
return r.sr.Next() | ||
} | ||
|
||
func (r *syncreplResponse) start(ctx context.Context, searchRequest *SearchRequest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mostly a copy of the ´startmethod for
SearchAsync`. I think we should minize any code duplication, as we already have a copy of basically everythig because of Go module compatibility.
I suggest to remove this duplicate and use a control with SearchAsync
instead. This would reduce the LOC dramatically, the amount of work required when thinks need to update and is more in line with the approaches used for regular search or modify requests.
The only think that needs to change is this addition to the start
method of SearchAsync
, to account for the syncrepl control:
case ApplicationIntermediateResponse:
decoded, err := DecodeSyncreplControl(packet.Children[1])
if err != nil {
werr := fmt.Errorf("failed to decode intermediate response: %w", err)
r.sr.ch <- &SearchSingleResult{Error: werr}
return
}
result := &SearchSingleResult{}
result.Controls = append(result.Controls, decoded)
r.sr.ch <- result
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to remove this duplicate and use a control with SearchAsync instead.
I also tried to implement the syncrepl feature into the searchResponse struct with SearchAsync() method. And then, I found we have to change several code places. Because existent search response packets and syncrepl search response packets are a little different. Not only ApplicationIntermediateResponse.
For example, compare handling ApplicationSearchResultEntry of the response packet. The searchResponse doesn't handle controls.
- searchResponse
switch packet.Children[1].Tag {
case ApplicationSearchResultEntry:
r.ch <- &SearchSingleResult{
Entry: &Entry{
DN: packet.Children[1].Children[0].Value.(string),
Attributes: unpackAttributes(packet.Children[1].Children[1].Children),
},
}
But, the syncreplResponse expects an entry with a control. To implement the syncrepl functions, I thought it might change existing behavior (I'm not sure whether the existing behavior is correct or not). Do you think this is acceptable?
- syncreplResponse
case ApplicationSearchResultEntry:
result := &SearchSingleResult{
Entry: &Entry{
DN: packet.Children[1].Children[0].Value.(string),
Attributes: unpackAttributes(packet.Children[1].Children[1].Children),
},
}
if len(packet.Children) != 3 {
r.sr.ch <- result
continue
}
controlPacket := packet.Children[2].Children[0]
decoded, err := DecodeSyncreplControl(controlPacket)
if err != nil {
werr := fmt.Errorf("failed to decode search result entry: %w", err)
result.Error = werr
r.sr.ch <- result
return
}
result.Controls = append(result.Controls, decoded)
r.sr.ch <- result
I think adding the syncrepl feature makes increasing some check condition codes to work them together (both normal search and syncrepl consumer). I can implement them, but I'm not sure whether some side effects affect a normal search or not. This is a trade-off between maintenance cost and compatibility/efficiency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that reducing unnecessary code duplication is a worthy goal, in this case I'm okay with a distinct syncreplResponse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cpuschma I integrated the source code for maintainability. I did change the existing process carefully, so it is almost an additional function. There are only three parts to be changed. That is few than I expected. So I think you are right about maintainability.
Could you review it again?
I agree with @cpuschma; thank you for your work on this feature. My involvement with this project is very limited now, so I'm okay with deferring to the other @go-ldap/committers as far as requested changes. My overarching values are:
With this in mind, my vote is to approve this MR |
@johnweldon Thank you for reviewing! I agree with your idea. And then, I will try to implement this PR without duplicating the code this weekend. I think it's possible, but it might be a little complex. I will carefully integrate my new code to prevent the side effect. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, please see the two notes. After that we can merge the PR
After I discussed it with @cpuschma, I changed the design for maintainability.
Design
I added the syncrepl feature as one of the asynchronous search functions. The syncrepl is available on slapd in OpenLDAP project and it provides a persistent search. The syncrepl is proposed by rfc-4533.
(the below description is the original when I created this PR)
(Old) Design
At first, I tried to implement syncrepl feature into the same source. For example, the searchResponse struct has an asynchronous search process, DecodeControl() function has to decode various controls. However, I am concerned that they are challenging to handle syncrepl communications with other generic LDAP communications.
It has a complicated specification because syncrepl is proposed by rfc-4533. I think dividing existent control.go/response.go and related syncrepl code is stable and maintainable (Of course both belong to the same
ldap
package). Additionally, they won't put a bug on each other.Data flow
syncrepl has 4 Controls:
Controls
Sync Request Control as the following.
Sync Request with mode=RefreshAndPersist
Got entries and Sync Info Control as the following.
When a user is added
Got the entry and a Sync State Control such as the following.
When a user is modified
Got the entry and a Sync State Control such as the following.
When a user is deleted
Got the entry and a Sync State Control such as the following.
Sync Request with mode=RefreshOnly
Got entries and a Sync Done Control such as the following.
How to test
To enable syncrepl on the server side, add the below configuration into slapd.conf.
Then, call
Syncrepl()
method described in examples_test.go. I confirmed syncrepl feature works with OpenLDAP server 2.4/2.5 in my environment.I need more test patterns or more considerations since I'm new to LDAP protocol. Could you review it?
References