-
Notifications
You must be signed in to change notification settings - Fork 9.8k
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
tests/robustness: Split model code into deterministic and non-deterministic #15819
tests/robustness: Split model code into deterministic and non-deterministic #15819
Conversation
37b2d89
to
49f84cb
Compare
Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
49f84cb
to
9965bc1
Compare
PTAL @ahrtr |
tests/robustness/model/model.go
Outdated
// applyFailedRequest handles a failed requests, one that it's not known if it was persisted or not. | ||
func applyFailedRequest(states PossibleStates, request EtcdRequest) PossibleStates { | ||
// stepFailedRequest duplicates number of states by considering request persisted and lost. | ||
func (states nonDeterministicState) stepFailedRequest(request EtcdRequest) nonDeterministicState { |
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.
You want change the receiver's state, but it isn't a pointer receiver. So a function makes more sense than a method here. This comment applies the stepSuccessfulRequest
as well.
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 think it's useful for clarity to have a both models implement Step
method. It makes it more natural to understand when compared to have stepDeterministicModel
and stepNonDeterministicModel
.
Fact that I don't use pointer receiver and return new state is just a design decision that state is non mutable. I think it's ok that methods to be non mutable.
This's a huge PR, and it's hard to carefully review each line of code change. Please try to breakdown such huge PR as possible as you can next time. I will approve this PR for now given:
|
Before I approve this PR, please other members also take a look. cc @chaochn47 @fuweid @jmhbnz @ptabor if possible. |
tests/robustness/model/model.go
Outdated
func applyFailedRequest(states PossibleStates, request EtcdRequest) PossibleStates { | ||
// stepFailedRequest duplicates number of states by considering request persisted and lost. | ||
func (states nonDeterministicState) stepFailedRequest(request EtcdRequest) nonDeterministicState { | ||
newStates := make(nonDeterministicState, 0, len(states)*2) |
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.
you are doubling number of states, is there a scenario that the number will grow very quickly and checker won't be able to handle it?
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 guess stepSuccessfulRequest
would remove possible states. One successful response should clean up all previous possible states generated back to back.
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.
You are both right. NonDeterministic model can grow exponentially with number of operations. This however mostly happens on failed/error (wanna guess when it can happen on request success?). As @chaochn47 mentioned successful requests will cleanup previous possible states.
So the problem is that we should avoid long sequences of failed requests as they cause complexity to explode. I have mitigated the problem by introducing history patching #15078 #15598
if !reflect.DeepEqual(newState, s) { | ||
states = append(states, newState) |
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.
nit: I see that the check was added in different PR, but can you add a comment why we need !reflect.DeepEqual(newState, s)
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 guess it is just used to avoid same states. Please confirm.
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.
If the request doesn't impact the state then there is no reason to fork it. Example is failed defrag request.
@@ -295,11 +295,15 @@ func (h *AppendableHistory) AppendDefragment(start, end time.Duration, resp *cli | |||
h.appendFailed(request, start, err) | |||
return | |||
} | |||
var revision int64 |
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.
adding revision to defragmentResponse is nice to have, not critical for this PR. I that correct? Just trying to understand
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.
It is based on other code changes due to some shortcuts I took. By separating models I separate EtcdResponse
and EtcdNonDeterministicResponse
which means that now EtcdResponse
needs to have accurate Revision
, which means that I need to make sure that defragmentResponse returns response with accurate revision.
LGTM, It's a clean refactor from my point of view. The changed lines look scary though. No regression spotted from my eyes. |
tests/robustness/model/base.go
Outdated
if response.Txn.TxnResult { | ||
return state | ||
} |
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.
For my understanding, could you please explain why the initial state is empty if TxnResult
is true?
I understand it is the same as before.
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.
Ehm, embarrassing story, apparently the old me decided that TxnResult = !success
, because I wanted default Txn
struct to reflect transaction success.
tests/robustness/model/base.go
Outdated
newKVs := map[string]ValueRevision{} | ||
for k, v := range s.KeyValues { | ||
newKVs[k] = v | ||
} | ||
s.KeyValues = newKVs |
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.
It seems redundant between newKVs
and s.KeyValues
. Could you please clarify what's the usage of newKVs
?
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.
Avoid modifying the s.KeyValues
, I need to copy whole map. Would love to get snapshotable map to clean this up.
tests/robustness/model/base.go
Outdated
case LeaseRevoke: | ||
//Delete the keys attached to the lease | ||
keyDeleted := false | ||
for key := range s.Leases[request.LeaseRevoke.LeaseID].Keys { |
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.
For my understanding, lease in robustness base model has indefinite TTL, correct?
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.
Yes for now we don't test lease expiration by time, just by revoke call.
tests/robustness/model/base.go
Outdated
} | ||
|
||
type TxnResponse struct { | ||
TxnResult bool |
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.
It would be better if you can document the meaning of this field in future PRs. Thanks ~
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.
Will fix it in following PR, sorry for this abomination in naming.
if !reflect.DeepEqual(newState, s) { | ||
states = append(states, newState) |
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 guess it is just used to avoid same states. Please confirm.
tests/robustness/model/model.go
Outdated
func applyFailedRequest(states PossibleStates, request EtcdRequest) PossibleStates { | ||
// stepFailedRequest duplicates number of states by considering request persisted and lost. | ||
func (states nonDeterministicState) stepFailedRequest(request EtcdRequest) nonDeterministicState { | ||
newStates := make(nonDeterministicState, 0, len(states)*2) |
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 guess stepSuccessfulRequest
would remove possible states. One successful response should clean up all previous possible states generated back to back.
Sorry for large PR, I didn't have time to split it and was worried that it would only make review take longer. As code structure clarifies it should be easier to make small independent changes. FYI I'm pretty sure that this change is correct as etcd model have absurd number of tests and any error would result would immediately result in robustness tests failing. Main motivation for this was exponential growth of tests for non-deterministic model. If you look through scenarios I test every combination of requests both their success and failure. Separating deterministic model tests allows me to just implement tests for request success. |
…nistic Signed-off-by: Marek Siarkowicz <siarkowicz@google.com> Co-authored-by: Benjamin Wang <wachao@vmware.com> Co-authored-by: chao <54131596+chaochn47@users.noreply.github.com>
62c69ae
to
92366a5
Compare
@serathius Please ping me if you resolve all comments, I will take another look so that this PR can merged today. |
I resolved all comments, however I didn't mark all of them as "done", as some of them included questions and I want to give people chance to read answers. |
Big refactor to separate deterministic etcd model (assumes all request succeed) and non-deterministic (considers failed request both persisted and not). This way a non-deterministic model just layer above deterministic one.
cc @ahrtr @ptabor