Skip to content

Commit

Permalink
License removal leads back to a basic license (elastic#52407)
Browse files Browse the repository at this point in the history
A new basic license will be generated when existing license is deleted.
In addition, deleting an existing basic license is a no-op.

Resolves: elastic#45022
  • Loading branch information
ywangd authored Feb 21, 2020
1 parent 72cf242 commit 0d62213
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateListener;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.service.ClusterService;
Expand Down Expand Up @@ -340,29 +339,10 @@ public void triggered(SchedulerEngine.Event event) {
/**
* Remove license from the cluster state metadata
*/
public void removeLicense(final DeleteLicenseRequest request, final ActionListener<ClusterStateUpdateResponse> listener) {
public void removeLicense(final DeleteLicenseRequest request, final ActionListener<PostStartBasicResponse> listener) {
final PostStartBasicRequest startBasicRequest = new PostStartBasicRequest().acknowledge(true);
clusterService.submitStateUpdateTask("delete license",
new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(request, listener) {
@Override
protected ClusterStateUpdateResponse newResponse(boolean acknowledged) {
return new ClusterStateUpdateResponse(acknowledged);
}

@Override
public ClusterState execute(ClusterState currentState) throws Exception {
MetaData metaData = currentState.metaData();
final LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
if (currentLicenses.getLicense() != LicensesMetaData.LICENSE_TOMBSTONE) {
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
LicensesMetaData newMetadata = new LicensesMetaData(LicensesMetaData.LICENSE_TOMBSTONE,
currentLicenses.getMostRecentTrialVersion());
mdBuilder.putCustom(LicensesMetaData.TYPE, newMetadata);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
} else {
return currentState;
}
}
});
new StartBasicClusterTask(logger, clusterService.getClusterName().value(), clock, startBasicRequest, listener));
}

public License getLicense() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,20 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
XPackPlugin.checkReadyForXPackCustomMetadata(currentState);
LicensesMetaData licensesMetaData = currentState.metaData().custom(LicensesMetaData.TYPE);
License currentLicense = LicensesMetaData.extractLicense(licensesMetaData);
if (currentLicense == null || License.LicenseType.isBasic(currentLicense.type()) == false) {
long issueDate = clock.millis();
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
License.Builder specBuilder = License.builder()
.uid(UUID.randomUUID().toString())
.issuedTo(clusterName)
.maxNodes(LicenseService.SELF_GENERATED_LICENSE_MAX_NODES)
.issueDate(issueDate)
.type(License.LicenseType.BASIC)
.expiryDate(LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS);
License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes());
LicensesMetaData currentLicensesMetaData = currentState.metaData().custom(LicensesMetaData.TYPE);
License currentLicense = LicensesMetaData.extractLicense(currentLicensesMetaData);
if (shouldGenerateNewBasicLicense(currentLicense)) {
License selfGeneratedLicense = generateBasicLicense(currentState);
if (request.isAcknowledged() == false && currentLicense != null) {
Map<String, String[]> ackMessages = LicenseService.getAckMessages(selfGeneratedLicense, currentLicense);
if (ackMessages.isEmpty() == false) {
this.ackMessages.set(ackMessages);
return currentState;
}
}
Version trialVersion = null;
if (licensesMetaData != null) {
trialVersion = licensesMetaData.getMostRecentTrialVersion();
}
Version trialVersion = currentLicensesMetaData != null ? currentLicensesMetaData.getMostRecentTrialVersion() : null;
LicensesMetaData newLicensesMetaData = new LicensesMetaData(selfGeneratedLicense, trialVersion);
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
} else {
Expand All @@ -98,4 +87,23 @@ public void onFailure(String source, @Nullable Exception e) {
logger.error(new ParameterizedMessage("unexpected failure during [{}]", source), e);
listener.onFailure(e);
}

private boolean shouldGenerateNewBasicLicense(License currentLicense) {
return currentLicense == null
|| License.LicenseType.isBasic(currentLicense.type()) == false
|| LicenseService.SELF_GENERATED_LICENSE_MAX_NODES != currentLicense.maxNodes()
|| LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS != currentLicense.expiryDate();
}

private License generateBasicLicense(ClusterState currentState) {
final License.Builder specBuilder = License.builder()
.uid(UUID.randomUUID().toString())
.issuedTo(clusterName)
.maxNodes(LicenseService.SELF_GENERATED_LICENSE_MAX_NODES)
.issueDate(clock.millis())
.type(License.LicenseType.BASIC)
.expiryDate(LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS);

return SelfGeneratedLicense.create(specBuilder, currentState.nodes());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
Expand Down Expand Up @@ -56,10 +55,10 @@ protected ClusterBlockException checkBlock(DeleteLicenseRequest request, Cluster
@Override
protected void masterOperation(Task task, final DeleteLicenseRequest request, ClusterState state,
final ActionListener<AcknowledgedResponse> listener) throws ElasticsearchException {
licenseService.removeLicense(request, new ActionListener<ClusterStateUpdateResponse>() {
licenseService.removeLicense(request, new ActionListener<PostStartBasicResponse>() {
@Override
public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) {
listener.onResponse(new AcknowledgedResponse(clusterStateUpdateResponse.isAcknowledged()));
public void onResponse(PostStartBasicResponse postStartBasicResponse) {
listener.onResponse(new AcknowledgedResponse(postStartBasicResponse.isAcknowledged()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;

@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0, maxNumDataNodes = 0)
public class LicenseServiceClusterTests extends AbstractLicensesIntegrationTestCase {
Expand Down Expand Up @@ -73,14 +72,14 @@ public void testClusterRestartWithLicense() throws Exception {
assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(license));
logger.info("--> remove licenses");
licensingClient.prepareDeleteLicense().get();
assertOperationMode(License.OperationMode.MISSING);
assertOperationMode(License.OperationMode.BASIC);

logger.info("--> restart all nodes");
internalCluster().fullRestart();
licensingClient = new LicensingClient(client());
ensureYellow();
assertThat(licensingClient.prepareGetLicense().get().license(), nullValue());
assertOperationMode(License.OperationMode.MISSING);
assertTrue(License.LicenseType.isBasic(licensingClient.prepareGetLicense().get().license().type()));
assertOperationMode(License.OperationMode.BASIC);


wipeAllLicenses();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package org.elasticsearch.license;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
Expand Down Expand Up @@ -120,16 +119,16 @@ public void testRemoveLicenses() throws Exception {
// remove signed licenses
removeAndAckSignedLicenses(licenseService);
licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licensesMetaData.getLicense(), equalTo(LicensesMetaData.LICENSE_TOMBSTONE));
assertTrue(License.LicenseType.isBasic(licensesMetaData.getLicense().type()));
}

private void removeAndAckSignedLicenses(final LicenseService licenseService) {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
licenseService.removeLicense(new DeleteLicenseRequest(), new ActionListener<ClusterStateUpdateResponse>() {
licenseService.removeLicense(new DeleteLicenseRequest(), new ActionListener<PostStartBasicResponse>() {
@Override
public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) {
if (clusterStateUpdateResponse.isAcknowledged()) {
public void onResponse(PostStartBasicResponse postStartBasicResponse) {
if (postStartBasicResponse.isAcknowledged()) {
success.set(true);
}
latch.countDown();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public void testRemoveLicensesSimple() throws Exception {
assertThat(deleteLicenseResponse.isAcknowledged(), equalTo(true));
// get licenses (expected no licenses)
getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertNull(getLicenseResponse.license());
assertTrue(License.LicenseType.isBasic(getLicenseResponse.license().type()));
}

public void testLicenseIsRejectWhenStartDateLaterThanNow() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@ public void testStartBasicLicense() throws Exception {
assertEquals("trial", getLicenseResponse.license().type());
});

// Testing that you can start a basic license when you have no license
if (randomBoolean()) {
licensingClient.prepareDeleteLicense().get();
assertNull(licensingClient.prepareGetLicense().get().license());
}

RestClient restClient = getRestClient();
Response response = restClient.performRequest(new Request("GET", "/_license/basic_status"));
String body = Streams.copyToString(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,23 @@ teardown:
- length: { license: 12 }
- match: { license.uid: "893361dc-9749-4997-93cb-802e3dofh7aa" }
---
"Should throw 404 after license deletion":
"Should revert back to basic license after license deletion":
- do:
license.delete: {}

- match: { acknowledged: true }

- do:
license.get: {}
catch: missing

- match: { license.type: "basic" }
- set: { license.uid: id }

- do: # delete an existing basic license is a no-op
license.delete: {}
- do:
license.get: {}
- match: { license.uid: $id}

---
"Should install a feature type license":
Expand Down

0 comments on commit 0d62213

Please sign in to comment.