Skip to content
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 remote license checker to use LicensedFeature #79876

Merged
merged 20 commits into from
Oct 28, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.indices.IndexClosedException;
import org.elasticsearch.license.RemoteClusterLicenseChecker;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.xpack.ccr.action.ShardChangesAction;
import org.elasticsearch.xpack.core.ClientHelper;
Expand Down Expand Up @@ -231,7 +230,7 @@ private void checkRemoteClusterLicenseAndFetchClusterState(
final Function<RemoteClusterLicenseChecker.LicenseCheck, ElasticsearchStatusException> nonCompliantLicense,
final Function<Exception, ElasticsearchStatusException> unknownLicense) {
// we have to check the license on the remote cluster
new RemoteClusterLicenseChecker(client, XPackLicenseState::isCcrAllowedForOperationMode).checkRemoteClusterLicenses(
new RemoteClusterLicenseChecker(client, CcrConstants.CCR_FEATURE).checkRemoteClusterLicenses(
Collections.singletonList(clusterAlias),
new ActionListener<RemoteClusterLicenseChecker.LicenseCheck>() {

Expand Down Expand Up @@ -428,9 +427,8 @@ private static ElasticsearchStatusException indexMetadataNonCompliantRemoteLicen
leaderIndex,
clusterAlias,
RemoteClusterLicenseChecker.buildErrorMessage(
"ccr",
licenseCheck.remoteClusterLicenseInfo(),
RemoteClusterLicenseChecker::isAllowedByLicense));
CcrConstants.CCR_FEATURE,
licenseCheck.remoteClusterLicenseInfo()));
return new ElasticsearchStatusException(message, RestStatus.BAD_REQUEST);
}

Expand All @@ -442,9 +440,8 @@ private static ElasticsearchStatusException clusterStateNonCompliantRemoteLicens
"can not fetch remote cluster state as the remote cluster [%s] is not licensed for [ccr]; %s",
clusterAlias,
RemoteClusterLicenseChecker.buildErrorMessage(
"ccr",
licenseCheck.remoteClusterLicenseInfo(),
RemoteClusterLicenseChecker::isAllowedByLicense));
CcrConstants.CCR_FEATURE,
licenseCheck.remoteClusterLicenseInfo()));
return new ElasticsearchStatusException(message, RestStatus.BAD_REQUEST);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static org.elasticsearch.license.XPackLicenseState.isAllowedByOperationMode;

/**
* Checks remote clusters for license compatibility with a specified license predicate.
* Checks remote clusters for license compatibility with a specified licensed feature.
*/
public final class RemoteClusterLicenseChecker {

Expand Down Expand Up @@ -125,23 +126,18 @@ private LicenseCheck(final RemoteClusterLicenseInfo remoteClusterLicenseInfo) {

private static final ClusterNameExpressionResolver clusterNameExpressionResolver = new ClusterNameExpressionResolver();
private final Client client;
private final Predicate<License.OperationMode> predicate;
private final LicensedFeature feature;

/**
* Constructs a remote cluster license checker with the specified license predicate for checking license compatibility. The predicate
* does not need to check for the active license state as this is handled by the remote cluster license checker.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Constructs a remote cluster license checker with the specified license predicate for checking license compatibility. The predicate
* does not need to check for the active license state as this is handled by the remote cluster license checker.
* Constructs a remote cluster license checker with the specified licensed feature for checking license compatibility. The feature
* does not need to check for the active license state as this is handled by the remote cluster license checker.

*
* @param client the client
* @param predicate the license predicate
* @param feature the licensed feature
*/
public RemoteClusterLicenseChecker(final Client client, final Predicate<License.OperationMode> predicate) {
public RemoteClusterLicenseChecker(final Client client, final LicensedFeature feature) {
ywangd marked this conversation as resolved.
Show resolved Hide resolved
this.client = client;
this.predicate = predicate;
}

public static boolean isAllowedByLicense(final XPackInfoResponse.LicenseInfo licenseInfo) {
final License.OperationMode mode = License.OperationMode.parse(licenseInfo.getMode());
return XPackLicenseState.isAllowedByOperationMode(mode, License.OperationMode.PLATINUM);
this.feature = feature;
}

/**
Expand Down Expand Up @@ -169,8 +165,10 @@ public void onResponse(final XPackInfoResponse xPackInfoResponse) {
listener.onFailure(new ResourceNotFoundException("license info is missing for cluster [" + clusterAlias.get() + "]"));
return;
}
if ((licenseInfo.getStatus() == LicenseStatus.ACTIVE) == false
|| predicate.test(License.OperationMode.parse(licenseInfo.getMode())) == false) {

if (licenseInfo.getStatus() == LicenseStatus.ACTIVE == false
|| isAllowedByOperationMode(License.OperationMode.parse(licenseInfo.getMode()),
feature.getMinimumOperationMode()) == false) {
Copy link
Member

Choose a reason for hiding this comment

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

It's not a real issue but I feel a bit itchy that we don't check whether the feature actual needs an Active license here. It does and requiring active license is a norm going forward. But for the sake of clarity in this particular scope, I'd have something like

Suggested change
if (licenseInfo.getStatus() == LicenseStatus.ACTIVE == false
|| isAllowedByOperationMode(License.OperationMode.parse(licenseInfo.getMode()),
feature.getMinimumOperationMode()) == false) {
if ((feature.isNeedsActive() && licenseInfo.getStatus() == LicenseStatus.ACTIVE == false)
|| isAllowedByOperationMode(License.OperationMode.parse(licenseInfo.getMode()),
feature.getMinimumOperationMode()) == false) {

listener.onResponse(LicenseCheck.failure(new RemoteClusterLicenseInfo(clusterAlias.get(), licenseInfo)));
return;
}
Expand Down Expand Up @@ -273,20 +271,20 @@ public static List<String> remoteClusterAliases(final Set<String> remoteClusters
* @return an error message representing license incompatibility
*/
public static String buildErrorMessage(
final String feature,
final RemoteClusterLicenseInfo remoteClusterLicenseInfo,
final Predicate<XPackInfoResponse.LicenseInfo> predicate) {
final LicensedFeature feature,
final RemoteClusterLicenseInfo remoteClusterLicenseInfo) {
final StringBuilder error = new StringBuilder();
if (remoteClusterLicenseInfo.licenseInfo().getStatus() != LicenseStatus.ACTIVE) {
error.append(String.format(Locale.ROOT, "the license on cluster [%s] is not active", remoteClusterLicenseInfo.clusterAlias()));
Copy link
Member

Choose a reason for hiding this comment

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

Similarly, this assumes the feature requires an active license, which is true. But the assumption is somewhat disconnected.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've adjusted this so that if the feature is present, it checks the isNeedsActive(). It also checks if the feature is not present, to match the current behavior where an active license is still needed for the BASIC functionality.

} else {
assert predicate.test(remoteClusterLicenseInfo.licenseInfo()) == false : "license must be incompatible to build error message";
assert isAllowedByOperationMode(License.OperationMode.parse(remoteClusterLicenseInfo.licenseInfo().getMode()),
feature.getMinimumOperationMode()) == false : "license must be incompatible to build error message";
final String message = String.format(
Locale.ROOT,
"the license mode [%s] on cluster [%s] does not enable [%s]",
License.OperationMode.parse(remoteClusterLicenseInfo.licenseInfo().getMode()),
remoteClusterLicenseInfo.clusterAlias(),
feature);
feature.getName());
error.append(message);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,19 +451,11 @@ public Map<FeatureUsage, Long> getLastUsed() {
return usage.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> timeConverter.apply(e.getValue())));
}

public static boolean isMachineLearningAllowedForOperationMode(final OperationMode operationMode) {
return isAllowedByOperationMode(operationMode, OperationMode.PLATINUM);
}

public static boolean isFipsAllowedForOperationMode(final OperationMode operationMode) {
return isAllowedByOperationMode(operationMode, OperationMode.PLATINUM);
}

public static boolean isCcrAllowedForOperationMode(final OperationMode operationMode) {
return isAllowedByOperationMode(operationMode, OperationMode.PLATINUM);
}

public static boolean isAllowedByOperationMode(
static boolean isAllowedByOperationMode(
final OperationMode operationMode, final OperationMode minimumMode) {
if (OperationMode.TRIAL == operationMode) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.ValidationException;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.indices.InvalidIndexNameException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,8 @@ public void testCheckRemoteClusterLicensesGivenCompatibleLicenses() {
responses.add(new XPackInfoResponse(null, createPlatinumLicenseResponse(), null));
responses.add(new XPackInfoResponse(null, createPlatinumLicenseResponse(), null));

final RemoteClusterLicenseChecker licenseChecker =
new RemoteClusterLicenseChecker(client, operationMode ->
XPackLicenseState.isAllowedByOperationMode(operationMode, License.OperationMode.PLATINUM));
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "feature", License.OperationMode.PLATINUM);
final RemoteClusterLicenseChecker licenseChecker = new RemoteClusterLicenseChecker(client, feature);
final AtomicReference<RemoteClusterLicenseChecker.LicenseCheck> licenseCheck = new AtomicReference<>();

licenseChecker.checkRemoteClusterLicenses(
Expand Down Expand Up @@ -189,9 +188,8 @@ public void testCheckRemoteClusterLicensesGivenIncompatibleLicense() {
return null;
}).when(client).execute(same(XPackInfoAction.INSTANCE), any(), any());

final RemoteClusterLicenseChecker licenseChecker =
new RemoteClusterLicenseChecker(client, operationMode ->
XPackLicenseState.isAllowedByOperationMode(operationMode, License.OperationMode.PLATINUM));
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "feature", License.OperationMode.PLATINUM);
final RemoteClusterLicenseChecker licenseChecker = new RemoteClusterLicenseChecker(client, feature);
final AtomicReference<RemoteClusterLicenseChecker.LicenseCheck> licenseCheck = new AtomicReference<>();

licenseChecker.checkRemoteClusterLicenses(
Expand Down Expand Up @@ -236,9 +234,8 @@ public void testCheckRemoteClusterLicencesGivenNonExistentCluster() {
responses.add(new XPackInfoResponse(null, createPlatinumLicenseResponse(), null));
responses.add(new XPackInfoResponse(null, createPlatinumLicenseResponse(), null));

final RemoteClusterLicenseChecker licenseChecker =
new RemoteClusterLicenseChecker(client, operationMode ->
XPackLicenseState.isAllowedByOperationMode(operationMode, License.OperationMode.PLATINUM));
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "feature", License.OperationMode.PLATINUM);
final RemoteClusterLicenseChecker licenseChecker = new RemoteClusterLicenseChecker(client, feature);
final AtomicReference<Exception> exception = new AtomicReference<>();

licenseChecker.checkRemoteClusterLicenses(
Expand Down Expand Up @@ -277,9 +274,8 @@ public void testRemoteClusterLicenseCallUsesSystemContext() throws InterruptedEx
return null;
}).when(client).execute(same(XPackInfoAction.INSTANCE), any(), any());

final RemoteClusterLicenseChecker licenseChecker =
new RemoteClusterLicenseChecker(client, operationMode ->
XPackLicenseState.isAllowedByOperationMode(operationMode, License.OperationMode.PLATINUM));
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "feature", License.OperationMode.PLATINUM);
final RemoteClusterLicenseChecker licenseChecker = new RemoteClusterLicenseChecker(client, feature);

final List<String> remoteClusterAliases = Collections.singletonList("valid");
licenseChecker.checkRemoteClusterLicenses(
Expand Down Expand Up @@ -317,9 +313,8 @@ public void testListenerIsExecutedWithCallingContext() throws InterruptedExcepti
responses.add(new XPackInfoResponse(null, createPlatinumLicenseResponse(), null));
responses.add(new XPackInfoResponse(null, createPlatinumLicenseResponse(), null));

final RemoteClusterLicenseChecker licenseChecker =
new RemoteClusterLicenseChecker(client, operationMode ->
XPackLicenseState.isAllowedByOperationMode(operationMode, License.OperationMode.PLATINUM));
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "feature", License.OperationMode.PLATINUM);
final RemoteClusterLicenseChecker licenseChecker = new RemoteClusterLicenseChecker(client, feature);

final AtomicBoolean listenerInvoked = new AtomicBoolean();
threadPool.getThreadContext().putHeader("key", "value");
Expand Down Expand Up @@ -359,27 +354,30 @@ public void testBuildErrorMessageForActiveCompatibleLicense() {
final XPackInfoResponse.LicenseInfo platinumLicence = createPlatinumLicenseResponse();
final RemoteClusterLicenseChecker.RemoteClusterLicenseInfo info =
new RemoteClusterLicenseChecker.RemoteClusterLicenseInfo("platinum-cluster", platinumLicence);
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "foo", License.OperationMode.PLATINUM);
final AssertionError e = expectThrows(
AssertionError.class,
() -> RemoteClusterLicenseChecker.buildErrorMessage("", info, RemoteClusterLicenseChecker::isAllowedByLicense));
() -> RemoteClusterLicenseChecker.buildErrorMessage(feature, info));
assertThat(e, hasToString(containsString("license must be incompatible to build error message")));
}

public void testBuildErrorMessageForIncompatibleLicense() {
final XPackInfoResponse.LicenseInfo basicLicense = createBasicLicenseResponse();
final RemoteClusterLicenseChecker.RemoteClusterLicenseInfo info =
new RemoteClusterLicenseChecker.RemoteClusterLicenseInfo("basic-cluster", basicLicense);
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "feature", License.OperationMode.PLATINUM);
assertThat(
RemoteClusterLicenseChecker.buildErrorMessage("Feature", info, RemoteClusterLicenseChecker::isAllowedByLicense),
equalTo("the license mode [BASIC] on cluster [basic-cluster] does not enable [Feature]"));
RemoteClusterLicenseChecker.buildErrorMessage(feature, info),
equalTo("the license mode [BASIC] on cluster [basic-cluster] does not enable [feature]"));
}

public void testBuildErrorMessageForInactiveLicense() {
final XPackInfoResponse.LicenseInfo expiredLicense = createExpiredLicenseResponse();
final RemoteClusterLicenseChecker.RemoteClusterLicenseInfo info =
new RemoteClusterLicenseChecker.RemoteClusterLicenseInfo("expired-cluster", expiredLicense);
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "foo", License.OperationMode.PLATINUM);
assertThat(
RemoteClusterLicenseChecker.buildErrorMessage("Feature", info, RemoteClusterLicenseChecker::isAllowedByLicense),
RemoteClusterLicenseChecker.buildErrorMessage(feature, info),
equalTo("the license on cluster [expired-cluster] is not active"));
}

Expand All @@ -393,9 +391,8 @@ public void testCheckRemoteClusterLicencesNoLicenseMetadata() {
return null;
}).when(client).execute(same(XPackInfoAction.INSTANCE), any(), any());

final RemoteClusterLicenseChecker licenseChecker =
new RemoteClusterLicenseChecker(client, operationMode ->
XPackLicenseState.isAllowedByOperationMode(operationMode, License.OperationMode.PLATINUM));
LicensedFeature.Momentary feature = LicensedFeature.momentary(null, "feature", License.OperationMode.PLATINUM);
final RemoteClusterLicenseChecker licenseChecker = new RemoteClusterLicenseChecker(client, feature);
final AtomicReference<Exception> exception = new AtomicReference<>();

licenseChecker.checkRemoteClusterLicenses(
Expand Down
Loading