Skip to content

Conversation

@gwbrown
Copy link
Contributor

@gwbrown gwbrown commented Oct 2, 2023

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.

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.
@gwbrown gwbrown added >refactoring :Security/License License functionality for commercial features v8.11.0 labels Oct 2, 2023
@gwbrown gwbrown requested review from jakelandis and rjernst October 2, 2023 23:44
@gwbrown gwbrown marked this pull request as ready for review October 2, 2023 23:45
@elasticsearchmachine elasticsearchmachine added the Team:Security Meta label for security team label Oct 2, 2023
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-security (Team:Security)

@gwbrown gwbrown changed the title License/decouple trial from version Decouple trial license resets from product version Oct 2, 2023
// 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());
Copy link
Contributor

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 ?

Copy link
Contributor Author

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 {
Copy link
Contributor

@jakelandis jakelandis Oct 3, 2023

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.

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor

@jakelandis jakelandis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mattc58 mattc58 added v8.12.0 and removed v8.11.0 labels Oct 4, 2023
Copy link
Member

@rjernst rjernst left a 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());
Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

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?

Copy link
Contributor Author

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";
Copy link
Member

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?

Copy link
Contributor Author

@gwbrown gwbrown Oct 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A string, unfortunately:

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.

Copy link
Member

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
@gwbrown gwbrown requested a review from rjernst October 27, 2023 22:23
Copy link
Member

@rjernst rjernst left a 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() {
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover "era"

@gwbrown
Copy link
Contributor Author

gwbrown commented Nov 2, 2023

Failure is #97260

@gwbrown
Copy link
Contributor Author

gwbrown commented Nov 2, 2023

@elasticmachine update branch

@gwbrown gwbrown added the auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) label Nov 2, 2023
@elasticsearchmachine elasticsearchmachine merged commit 900f109 into elastic:main Nov 2, 2023
@gwbrown gwbrown deleted the license/decouple-trial-from-version branch November 2, 2023 22:55
elasticsearchmachine pushed a commit that referenced this pull request Nov 29, 2023
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
timgrein pushed a commit to timgrein/elasticsearch that referenced this pull request Nov 30, 2023
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
elasticsearchmachine pushed a commit that referenced this pull request Apr 2, 2024
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.
elasticsearchmachine pushed a commit that referenced this pull request Jan 16, 2025
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) >refactoring :Security/License License functionality for commercial features Team:Security Meta label for security team v8.12.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants