-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
fix(crd-generator): Fail if multiple versions are marked as stored #5846
Conversation
…orAssertions utility class
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.
crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java
Show resolved
Hide resolved
if (spec.hasVersions() && !spec.hasMatchingVersion(hasStorageVersion)) { | ||
spec.editFirstVersion().withStorage(true).endVersion(); | ||
} | ||
List<String> storageVersions = versions.stream() |
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 recall that, at some point, someone unrolled the stream
s into plain loops because of this feature and I believe that we are not testing it quite enough.
As ugly as it may sound 😞 , have you tried those changes with that flag enabled?
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, that's why I added ParallelCRDGeneratorExamplesTest
. It should work for parallel generation, too.
You are also right it's not the nicest way, but it's also hard to improve the situation here.
The core problem is not that we are working e.g. with streams or a predicate or a atomic reference. (That's just optimization of the error output).
The real problem is that the decorator sort algorithm is not working as expected on "multi level after/before" dependencies.
That's why I changed...
@Override
public Class<? extends Decorator>[] after() {
return new Class[] { AddCustomResourceDefinitionResourceDecorator.class,
AddCustomResourceDefinitionVersionDecorator.class, SetStorageVersionDecorator.class };
}
Toggle me to see Decorator order...
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddCustomResourceDefinitionResourceDecorator [apiGroup=sample.fabric8.io, kind=Multiple, name=multiples.sample.fabric8.io, plural=multiples, scope=Cluster]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddCustomResourceDefinitionResourceDecorator [apiGroup=sample.fabric8.io, kind=Multiple, name=multiples.sample.fabric8.io, plural=multiples, scope=Cluster]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io, version:v1]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io, version:v2]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddSchemaToCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io, version:v1]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddSchemaToCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io, version:v2]
[main] TRACE io.fabric8.crd.generator.v1.decorator.EnsureSingleStorageVersionDecorator [name:multiples.sample.fabric8.io]
[main] TRACE io.fabric8.crd.generator.v1.decorator.EnsureSingleStorageVersionDecorator [name:multiples.sample.fabric8.io]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetDeprecatedVersionDecorator [name:multiples.sample.fabric8.io, version:v1, deprecated:false, deprecationWarning:null]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetDeprecatedVersionDecorator [name:multiples.sample.fabric8.io, version:v2, deprecated:false, deprecationWarning:null]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetServedVersionDecorator [name:multiples.sample.fabric8.io, version:v1, served:true]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetServedVersionDecorator [name:multiples.sample.fabric8.io, version:v2, served:true]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetStorageVersionDecorator [name:multiples.sample.fabric8.io, version:v1, storage:false]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetStorageVersionDecorator [name:multiples.sample.fabric8.io, version:v2, storage:true]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SortCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SortCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SortPrinterColumnsDecorator [name:multiples.sample.fabric8.io, version:v2]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SortPrinterColumnsDecorator [name:multiples.sample.fabric8.io, version:v1]
to
@Override
public Class<? extends Decorator>[] after() {
return new Class[] {
CustomResourceDefinitionVersionDecorator.class
};
}
Toggle me to see Decorator order...
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddCustomResourceDefinitionResourceDecorator [apiGroup=sample.fabric8.io, kind=Multiple, name=multiples.sample.fabric8.io, plural=multiples, scope=Cluster]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddCustomResourceDefinitionResourceDecorator [apiGroup=sample.fabric8.io, kind=Multiple, name=multiples.sample.fabric8.io, plural=multiples, scope=Cluster]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io, version:v1]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io, version:v2]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddSchemaToCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io, version:v1]
[main] TRACE io.fabric8.crd.generator.v1.decorator.AddSchemaToCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io, version:v2]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetDeprecatedVersionDecorator [name:multiples.sample.fabric8.io, version:v1, deprecated:false, deprecationWarning:null]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetDeprecatedVersionDecorator [name:multiples.sample.fabric8.io, version:v2, deprecated:false, deprecationWarning:null]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetServedVersionDecorator [name:multiples.sample.fabric8.io, version:v1, served:true]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetServedVersionDecorator [name:multiples.sample.fabric8.io, version:v2, served:true]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetStorageVersionDecorator [name:multiples.sample.fabric8.io, version:v1, storage:false]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SetStorageVersionDecorator [name:multiples.sample.fabric8.io, version:v2, storage:true]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SortPrinterColumnsDecorator [name:multiples.sample.fabric8.io, version:v2]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SortPrinterColumnsDecorator [name:multiples.sample.fabric8.io, version:v1]
[main] TRACE io.fabric8.crd.generator.v1.decorator.EnsureSingleStorageVersionDecorator [name:multiples.sample.fabric8.io]
[main] TRACE io.fabric8.crd.generator.v1.decorator.EnsureSingleStorageVersionDecorator [name:multiples.sample.fabric8.io]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SortCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io]
[main] TRACE io.fabric8.crd.generator.v1.decorator.SortCustomResourceDefinitionVersionDecorator [name:multiples.sample.fabric8.io]
The EnsureSingleStorageVersionDecorator
extends CustomResourceDefinitionDecorator
and because of that it is working on the CustomResourceDefinition
"level". Meanwhile the CustomResourceDefinitionVersionDecorator
and SetStorageVersionDecorator
extend CustomResourceDefinitionVersionDecorator
, which means that they are working on the CustomResourceDefinitionVersion "level".
So far as I understand the sorting algorithm of the decorators mixing of multiple levels is not supported.
Maybe we should improve this sorting algorithm (or document the behaviour)?
Or am I wrong?
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, that's why I added
ParallelCRDGeneratorExamplesTest
. It should work for parallel generation, too.
That's nice, but I'm afraid that the execution of the annotation processors in the tests is not the same as when triggered using the "real" annotation processor.
Probably, the only way to fully test is to spin a new module like this:
https://github.com/fabric8io/kubernetes-client/tree/main/crd-generator/test
but enabling parallelism.
Thanks a lot for the rest of the analysis, I agree with this:
Maybe we should improve this sorting algorithm (or document the behaviour)?
I see that someone had already been through this trouble:
kubernetes-client/crd-generator/api/src/main/java/io/fabric8/crd/generator/Resources.java
Lines 84 to 90 in 0c80a66
// We can't guarantee that `when `decorator a < b and b < c then a < c``. | |
// Why? | |
// Because our comparators express constraints on particular pairs and can't express the global order. | |
// So, in order to be accurate we need to compare each decorator, with ALL OTHER decorators. | |
// In other words we don't ANY sorting algorithm, we need bubble sort. | |
// We also might need it more than once. So, we'll do it as many times as we have to, till there are not more transformations. | |
// But hey, let's have an upper limit of 5 just to prevent infinite loops |
Hopefully, we can get another pair of eyes from @metacosm or @iocanel here.
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 added the additional test MultipleVersionsCRDTest and tried it locally with parallel enabled. It looks good to me.
Probably, the only way to fully test is to spin a new module like this:
https://github.com/fabric8io/kubernetes-client/tree/main/crd-generator/test
but enabling parallelism.
I agree, this could be an enhancement for the tests.
Should I add the additional module to this PR? Or can we implement this in a seperate PR, maybe together with #5850?
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.
can we implement this in a seperate PR
that's fine for me
Probably this fixes #5507 as well |
spec.editFirstVersion().withStorage(true).endVersion(); | ||
} | ||
List<String> storageVersions = versions.stream() | ||
.filter(v -> Optional.ofNullable(v.getStorage()).orElse(true)) |
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.
Not sure where we should document that if storage isn't specified, we default to it being true… (storage is required in the spec)
We probably also should document that people might now need to be more explicit in their annotations and set storage to false where needed.
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 endusers, it's already documented in the annotation itself:
https://github.com/fabric8io/kubernetes-client/blob/main/kubernetes-model-generator/kubernetes-model-common/src/main/java/io/fabric8/kubernetes/model/annotation/Version.java#L46
/**
* Whether this version corresponds to the persisted version for the associated CRD. Note that only one version can set
* {@code storage} to {@code true} for a given CRD.
*
* @return {@code true} if this version corresponds to the persisted version for the associated CRD, {@code false} otherwise
*/
boolean storage() default true; // <-- here
For (internal) CRDGenerator developers:
If a property like storage
is equal to the default value, it is usually set to null in the internal representation.
This allows to omit the property during the schema generation, so that only those properties end up in the schema for which the value is not equal to the schema default.
We could start with a DEVELOPMENT.md
file in the CRDGenerator package to document such internal behaviours. (There are a few more things which should be documented e.g. the sorting algorithm for the decorators...)
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 endusers, it's already documented in the annotation itself: https://github.com/fabric8io/kubernetes-client/blob/main/kubernetes-model-generator/kubernetes-model-common/src/main/java/io/fabric8/kubernetes/model/annotation/Version.java#L46
/** * Whether this version corresponds to the persisted version for the associated CRD. Note that only one version can set * {@code storage} to {@code true} for a given CRD. * * @return {@code true} if this version corresponds to the persisted version for the associated CRD, {@code false} otherwise */ boolean storage() default true; // <-- here
Right, I missed that. This might be good enough.
Thank you for your improvements and fixes. These are much appreciated! |
Quality Gate failedFailed conditions |
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, thx!
Description
Fixes a bug where the CRDGenerator allows generating CRDs with multiple versions marked as stored.
Additional changes:
(refer to simplelogger.properties during test runs)
Fixes #5845
Type of change
test, version modification, documentation, etc.)
Checklist