-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Decouple trial license resets from product version #100169
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
Decouple trial license resets from product version #100169
Conversation
Prior to this commit, a new trial license was allowed for a cluster for each major version. This was a reasonable strategy when we were releasing major versions on a highly regular cadence, but since those releases have slowed, we may wish to offer new trial licenses that are not tied to a major version release. This commit introduces the concept of a Trial Licence Era, which is independent from the Elasticsearch version. In order to avoid complications with serialization, the TrialLicenseEra class is serialized in a way that is wire-compatible with the general Version class, and maintains the existing behavior for trials started in released versions. Thorough unit tests are included to verify that TrialLicenseEra and Version are compatible in both streamed and XContent representations.
|
Pinging @elastic/es-security (Team:Security) |
| // This simulates a node of a version that supports transport eras receiving a message which is still using old-school Versions | ||
| private TrialLicenseEra versionToEraSerialization(Version version) throws IOException { | ||
| try (BytesStreamOutput output = new BytesStreamOutput()) { | ||
| output.setTransportVersion(TransportVersion.current()); |
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 the test also include Transport version prior to the introduction of TransportVersion ?
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 don't think that using a different version there changes anything in this test; being a unit test this can't really-actually check BWC so all it's really doing is feeding the output of writeTo of one into the StreamInput constructor of the other. If I'm wrong and there's some logic we should make sure is tested here please point me to it; this is an unorthodox thing I'm doing and I want to make sure all the bases are covered.
| * that's already used a trial and let it expire. This class controls when we do that. The serialization of this class is designed to | ||
| * maintain compatibility with old-school Elasticsearch versions (specifically the {@link org.elasticsearch.Version} class). | ||
| */ | ||
| public class TrialLicenseEra implements ToXContentFragment, Writeable { |
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: (feel free to ignore) I would prefer TrialLicenseVersion over TrialLicenseEra custom concept scoped versions will become (if not already) common place and I think Era will be the outlier w.r.t. naming.
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.
Hah, I used Era instead of Version to try to avoid arguments about introducing yet another Version class, but maybe those have already been had. I'm very open to calling it Version.
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.
+1 to using the Version suffix, it's all the rage
jakelandis
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.
LGTM
rjernst
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.
I have a couple thoughts on simplifying the serialized forms.
|
|
||
| public void testVersionCanParseAllEras() { | ||
| for (int i = 2; i <= TrialLicenseEra.CURRENT.asInt(); i++) { | ||
| Version.fromString(new TrialLicenseEra(i).asVersionString()); |
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.
Assume that Version will eventually go away, so I'm not sure what this test is adding anything useful. While we need to support bwc, we should not be beholden to maintain a semver-like string going forward.
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 can remove this test, I found it necessary to gain enough confidence in this solution to be comfortable PR'ing it but it might not be valuable going forward. We might need to maintain the string format, if for some reason we need old versions of ES to be able to read the XContent emitted by newer ones (see the other thread on that point).
| } | ||
|
|
||
| public TrialLicenseVersion(StreamInput in) throws IOException { | ||
| this.era = (in.readVInt() - 99) / 1000000; // Copied from the constructor of Version |
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.
In other new *Version forks from Version we have used the exact same serialization but diverging going forward (a bigger integer). Rather than try to reduce this down to the major version number from previous Version instances, can we pin the latest version id as the "current", and not do any manipulation to the id when read?
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, that is a better way of doing it - frankly this is the first thing that occurred to me and upon reflection that's obviously a better way of doing it. I'll make the updates.
|
|
||
| // pkg-private for testing | ||
| String asVersionString() { | ||
| return this + ".0.0"; |
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 use this bwc format only in bwc cases? Does the license maintain the version bit as a string or version id?
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.
A string, unfortunately:
elasticsearch/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensesMetadata.java
Line 155 in 878127c
| builder.field(Fields.TRIAL_LICENSE, trialVersion.toString()); |
(⬆️ code is from
main, to be clear).
Note this is LicensesMetadata, rather than in the license itself, so we only store one of these for the whole cluster but it's still in the persistent cluster state. We can probably switch to emitting an integer, are there are any cases where we'd need an old version of ES to be able to parse XContent emitted by a newer version? The main case I'm aware of where we need to read a LicensesMetadata XContent is restoring a snapshot, and you can't restore a snapshot taken by a newer version than you're currently on AFAIK.
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.
As long as we only upgrade the license metadata format after the min version of the cluster has stabilized in the upgrade, we should be ok.
- Fix a few more leftover `era`s - Remove serialization tests linked to `Version` - Use integer as-is instead of extracting major version - Disallow starting a trial license in a mixed-version cluster
rjernst
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.
LGTM. Only remaining nit is there are still quite a few uses of the "era" terminology in method and variable names.
|
|
||
| Version getMostRecentTrialVersion() { | ||
| return trialVersion; | ||
| TrialLicenseVersion getMostRecentTrialEra() { |
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.
leftover "era" terminology?
| Metadata currentMetadata = currentState.metadata(); | ||
| LicensesMetadata licensesMetadata = currentMetadata.custom(LicensesMetadata.TYPE); | ||
| Version trialVersion = null; | ||
| TrialLicenseVersion trialEra = null; |
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.
leftover "era" terminology?
| } | ||
| Version trialVersion = currentLicensesMetadata != null ? currentLicensesMetadata.getMostRecentTrialVersion() : null; | ||
| updatedLicensesMetadata = new LicensesMetadata(selfGeneratedLicense, trialVersion); | ||
| TrialLicenseVersion trialEra = currentLicensesMetadata != null ? currentLicensesMetadata.getMostRecentTrialEra() : null; |
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.
leftover "era"
| License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes()); | ||
| Version trialVersion = currentLicenseMetadata.getMostRecentTrialVersion(); | ||
| LicensesMetadata newLicenseMetadata = new LicensesMetadata(selfGeneratedLicense, trialVersion); | ||
| TrialLicenseVersion trialEra = currentLicenseMetadata.getMostRecentTrialEra(); |
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.
leftover "era"
|
Failure is #97260 |
|
@elasticmachine update branch |
This PR fixes the version check we perform to determine if a new trial can be started. For trial licenses, #100169 decoupled version tracking from the Elasticsearch version. In short, the new logic is to allow a new trial license each time we increment the trial license version counter (with some additional caveats around BWC). This means we should allow a new trial license if the version the previous trial was started on is _below_ the `CURRENT` version. This condition was previously reversed. This PR fixes the check. I've also opted to restrict `ableToStartNewTrial` to not take arbitrary input and instead to always compare to `CURRENT`. This allows us to avoid the unnecessary complexity of handling a BWC edge case where both trial versions are below the cut-over value (this is impossible in practice). Fixes: #102286
This PR fixes the version check we perform to determine if a new trial can be started. For trial licenses, elastic#100169 decoupled version tracking from the Elasticsearch version. In short, the new logic is to allow a new trial license each time we increment the trial license version counter (with some additional caveats around BWC). This means we should allow a new trial license if the version the previous trial was started on is _below_ the `CURRENT` version. This condition was previously reversed. This PR fixes the check. I've also opted to restrict `ableToStartNewTrial` to not take arbitrary input and instead to always compare to `CURRENT`. This allows us to avoid the unnecessary complexity of handling a BWC edge case where both trial versions are below the cut-over value (this is impossible in practice). Fixes: elastic#102286
This commit is a follow-up to the changes converting the trial license refresh logic to use `TrialLicenseVersion` (#100169), converting the remaining uses of `Version`, which are used to ensure wire compatibility, with `TransportVersion`. Incidentally, this commit also removes code for compatibility with 7.6.0 and earlier, as those versions can no longer be in a cluster with current-version nodes.
You are allowed to initiate a trial only if your cluster has not already activated a trial for the current major product version. For example, if you have already activated a trial for v6.0, you cannot start a new trial until v7.0. `LicensesMetadata` is a ClusterState custom that contains the license information for a cluster. If the cluster has exercised a trial there is a trial version field populated. During a major upgrade, we want to allow users to run a new trial to test the features on the new major. This PR updates the trial version in the license to allow to start a new trial on 9.0. When 9.0 is released we'll have no clusters that has already exercised the trial so this PR also removes some of the BWC handling that was added when [trial license version was decoupled from stack version](#100169).
Prior to this commit, a new trial license was allowed for a cluster for each major version. This was a reasonable strategy when we were releasing major versions on a highly regular cadence, but since those releases have slowed, we may wish to offer new trial licenses that are not tied to a major version release.
This commit introduces the concept of a Trial Licence Era, which is independent from the Elasticsearch version. In order to avoid complications with serialization, the TrialLicenseEra class is serialized in a way that is wire-compatible with the general Version class, and maintains the existing behavior for trials started in released versions. Thorough unit tests are included to verify that TrialLicenseEra and Version are compatible in both streamed and XContent representations.