-
Notifications
You must be signed in to change notification settings - Fork 1
Convert one field to declarative, e2e #61
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
Convert one field to declarative, e2e #61
Conversation
ed13360 to
7dd1318
Compare
staging/src/k8s.io/code-generator/cmd/validation-gen/validators/openapi.go
Outdated
Show resolved
Hide resolved
Looks like it accidentally got squashed out of the history. This will add it back: #67 EDIT: The |
staging/src/k8s.io/code-generator/cmd/validation-gen/validators/openapi.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/api/validate/schema_test.go
Outdated
Show resolved
Hide resolved
20de23e to
68d2246
Compare
|
As discussed in the WG meeting, I have reviewed the commits here related to adding the
LGTM for the commits there w/ one If these commits are split out into a separate PR and merged |
acd4df3 to
74bdacb
Compare
|
Tests added as PoC . |
a114b09 to
00237e8
Compare
8fb62b1 to
341189e
Compare
jpbetz
left a comment
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.
Amazing seeing this all come together end-to-end. Most of my comments are about code organization.
| if old == nil { | ||
| runtimetest.RunValidationForEachVersion(t, legacyscheme.Scheme, sets.Set[string]{}, obj, accumulate) | ||
| } else { | ||
| runtimetest.RunUpdateValidationForEachVersion(t, legacyscheme.Scheme, sets.Set[string]{}, obj, old, accumulate) | ||
| } |
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've favored having a separate validation and update-validation functions for consistency with how strategy is defined, but it has led to an increase of functions in the scheme, and in these test utilities. Should we reconsider this? The alternative would be to switch to a conversion where a nil oldObject always indicates create validation, and a non-nil oldObject indicates update validation.
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 it cuts down on duplication, I think the semantic is plenty obvious. But I am very close to it...
| all[gv] = errs | ||
| } | ||
| if old == nil { | ||
| runtimetest.RunValidationForEachVersion(t, legacyscheme.Scheme, sets.Set[string]{}, obj, accumulate) |
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.
To better support the pattern being established in this PR, I'm okay with replacing RunValidationForEachVersion with a function that returns a map[string]field.ErrorList{} directly.
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 this is OK as it is - it's not terrible, and it is contained in this one func. I will go witth whatever you think gets past deads2k :)
| // FIXME: move somewhere generic - pkg/api/testing? | ||
| func TestVersionedValidationByFuzzing(t *testing.T) { | ||
| for i := 0; i < *roundtrip.FuzzIters; i++ { | ||
| gv := schema.GroupVersion{Group: "", Version: "v1"} |
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.
Should gv be an arg? (or should a scheme be passed in and all versions of a group be tested?)
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 we move this to a more generic place (where?) then yes. As it is, it is embedded in the "core" group.
eae5842 to
49b7015
Compare
|
Pushed now with declarative defaults (and some open questions) and declarative optional. This still only handles ONE FIELD. |
All of our tags are expressed from the perspective of a client of the API, but the code we generate is for the server. Optional is tricky. A field which is marked as optional and does not have a default value is strictly optional. A client is allowed to not set it and the server will not give it a default value. Code which consumes it must handle that it might not have any value at all. A field which is marked as optional but has a default value is optional to clients, but required to the server. A client is allowed to not set it but the server will give it a default value. Code which consumes it can assume that it always has a value.
The existing test run both declarative and manual validation and it still passes.
Now we can emit comments which stick to functions instead of coming before or after the functions when emitting code. For followup: I think we can simplify FunctionGen and ValidationGen
76bfa14 to
ee2385b
Compare
|
Rebased and updated this PR. Aside from gates, there are a few open things. 2 commits marked "WIP" need attention.
|
I'm OK with this. The client-side implications are something I'm still digesting. The way I see it, the tags are fine; the tags convey sane semantics for both client and server. There is the wrinkle that if we support calling generated validation from clients, then not-yet-defaulted fields would fail validation... I can live with this limitation, but it's good to be aware of it. Peeking at +default... this means we must also migrate defaults to declarative as we migrate field validation to declarative so we don't loose required checks. No objection from me. The idea that validation is aware of defaulting +default is something I've always been OK with. Previous systems I've worked on all did this so maybe that's why I'm fine with it.
Just to make sure I'm understanding this- IF a field is non-pointer, and is also tagged with |
| } | ||
| return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresources: %v", obj, subresources))} | ||
| }) | ||
| scheme.AddValidationFunc((*corev1.ReplicationControllerList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}, subresources ...string) field.ErrorList { |
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 is benign but unnecessary. @aaron-prindle maybe track that we could elide this somehow?
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 mean elide the validation for List? I guess I didn't realize it was being generated, but at least it looks correct, LOL.
Yeah, we probably don't need to generate for those. We could extend the package-tag to be a sort expression, e.g. types which have TypeMeta and not ListMeta ? Today we have:
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/core/v1
It could become:
// +k8s:validation-gen=typesWith(TypeMeta) // AND...
// +k8s:validation-gen=typesWithout(ListMeta)
// +k8s:validation-gen-input=k8s.io/api/core/v1
It's still a little sloppy but probably OK?
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 could be good for @aaron-prindle or @yongruilin to get a little deeper into the code-generator itself. Probably P2, though - not REQUIRED for 1.33?
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.
Agree, not required. Best to track it and fix when we have cycles. Short term- keep pushing on the critical path!
Precisely. I wrote a long comment about it: |
staging/src/k8s.io/code-generator/cmd/validation-gen/validators/required.go
Outdated
Show resolved
Hide resolved
| // But if the default is the zero value, then the zero value is obviously | ||
| // valid, and the fact that the field is optional is meaningless - there is | ||
| // no way to tell the difference between a client not setting it (yielding | ||
| // the zero value) and a client setting it to the zero value. |
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.
Thanks for the detailed write up.
ee2385b to
76f6175
Compare
|
Fix deprecated tag call. Still considering to move the zero-val logic back to gengo. |
|
I'm OK to merge this to dev branch if it passes reviews. When we go to k/k I think we want a distinct PR with a subset of the commits here + followups with gate changes, to illustrate how conversion is done. Or we can sit on this, get the gate and verification changes in, and then rebase this so the eventual k/k PR is easier? I am fine either way. |
|
LGTM. I'm fine with this merging. I'm don't foresee any major snags caused by merging this before gates or verification, but if anyone else does, I'll defer to them. |
|
Currently in this PR the feature gate - DeclarativeValidation is added and implemented From recent conversatios we mentioned changing the gate logic and names - would updating the logic here to use EnableDeclarativeValidatoin and DisableImperativeDeclarativeOverlap be another PR that we should track or is this planned as part of the PR here? |
|
@aaron-prindle I can change the semantics here or we can merge it and you or I can change it along with the verification. Your call. |
I'm fine with merging as is and then in a follow-up PR changing the semantics 👍 |
chore: Refactor validation comparison functions to use DirectEqual
This PR is WIP and should maybe be a few PRs. It should be reviewed commit-by-commit.
Goal: disable all generation for "real" APIs and prove that a single field of a single struct can be handled. Hopefully lay out a roadmap for future such conversion.
I looked for a field that could be handled by the existing tags, whose validation was trivial (no dependent fields, no other places checking the value, etc). I couldn't find one in core! Even simple enums had complicated validation. So I added
+k8s:minimum.Second: Add declarative validation for
ReplicationController.Spec.Replicaswhich does satisfy the above "trivial" criteria.Third: Document the testing to prove that this change is "safe". It proved that the test itself was crap, so I had to fix that first.
Fourth: Gate the manual validation of this field.
Fifth: Prove that the union of manual and declarative validation passes tests.
Sixth: Add tests that ensure the equivalence of versioned validation for manual and fuzzed inputs.
STATUS: As of now, the tests pass.
We need to decide if we will keep manually-written tests against generated validation in the long term. We don't, for example, test generated conversions, etc. In the short term, we obviously need to. Perhaps validation testing should move to test against strategy?
We need to figure out how we will do small, obvious conversions when things like
enumandunionoften have complicated manual validation.We need to figure out the relationship with defaulting, so that generated validation for
+k8s:optionalis different than+k8s:optional" ++defaultand when+k8s:required` is actually needed.