Internal: Simplify creation of trial license

Currently each node monitors the cluster state for a license, and if it
does not find one, it sends a request to the master to generate a trial
license. However, the master node has this same logic. Since the master
node is the only thing that can change the cluster state, we know that
once some node becomes master, it will notice the lack of license,
generate a trial license, and send a cluster state update. The trigger
from every node telling the master to generate the trial license is not
needed.

This change removes the register_trial_license action that the non
master nodes used. It removes the need for the TransportService in the
LicensesService, which will help with deguicing.

Original commit: elastic/x-pack-elasticsearch@a71656847e
This commit is contained in:
Ryan Ernst 2016-07-11 22:29:34 -07:00
parent 149df1fd44
commit a9ace27107
6 changed files with 54 additions and 80 deletions

View File

@ -15,7 +15,6 @@ import org.elasticsearch.cluster.ClusterStateListener;
import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.ClusterStateUpdateTask;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle; import org.elasticsearch.common.component.Lifecycle;
@ -31,13 +30,6 @@ import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest; import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.EmptyTransportResponseHandler;
import org.elasticsearch.transport.TransportChannel;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.transport.TransportRequestHandler;
import org.elasticsearch.transport.TransportResponse;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.scheduler.SchedulerEngine; import org.elasticsearch.xpack.scheduler.SchedulerEngine;
import org.elasticsearch.xpack.support.clock.Clock; import org.elasticsearch.xpack.support.clock.Clock;
@ -74,12 +66,11 @@ import java.util.concurrent.atomic.AtomicReference;
public class LicensesService extends AbstractLifecycleComponent implements ClusterStateListener, LicensesManagerService, public class LicensesService extends AbstractLifecycleComponent implements ClusterStateListener, LicensesManagerService,
LicenseeRegistry, SchedulerEngine.Listener { LicenseeRegistry, SchedulerEngine.Listener {
public static final String REGISTER_TRIAL_LICENSE_ACTION_NAME = "internal:plugin/license/cluster/register_trial_license"; // pkg private for tests
static final TimeValue TRIAL_LICENSE_DURATION = TimeValue.timeValueHours(30 * 24);
private final ClusterService clusterService; private final ClusterService clusterService;
private final TransportService transportService;
/** /**
* Currently active consumers to notify to * Currently active consumers to notify to
*/ */
@ -97,11 +88,6 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
*/ */
private List<ExpirationCallback> expirationCallbacks = new ArrayList<>(); private List<ExpirationCallback> expirationCallbacks = new ArrayList<>();
/**
* Duration of generated trial license
*/
private TimeValue trialLicenseDuration = TimeValue.timeValueHours(30 * 24);
/** /**
* Max number of nodes licensed by generated trial license * Max number of nodes licensed by generated trial license
*/ */
@ -120,14 +106,9 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
"please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:"; "please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:";
@Inject @Inject
public LicensesService(Settings settings, ClusterService clusterService, TransportService transportService, Clock clock) { public LicensesService(Settings settings, ClusterService clusterService, Clock clock) {
super(settings); super(settings);
this.clusterService = clusterService; this.clusterService = clusterService;
this.transportService = transportService;
if (DiscoveryNode.isMasterNode(settings)) {
transportService.registerRequestHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME, TransportRequest.Empty::new,
ThreadPool.Names.SAME, new RegisterTrialLicenseRequestHandler());
}
populateExpirationCallbacks(); populateExpirationCallbacks();
this.clock = clock; this.clock = clock;
this.scheduler = new SchedulerEngine(clock); this.scheduler = new SchedulerEngine(clock);
@ -354,7 +335,7 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
* has no signed/trial license * has no signed/trial license
*/ */
private void registerTrialLicense() { private void registerTrialLicense() {
clusterService.submitStateUpdateTask("generate trial license for [" + trialLicenseDuration + "]", new ClusterStateUpdateTask() { clusterService.submitStateUpdateTask("generate trial license for [" + TRIAL_LICENSE_DURATION + "]", new ClusterStateUpdateTask() {
@Override @Override
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
LicensesMetaData licensesMetaData = newState.metaData().custom(LicensesMetaData.TYPE); LicensesMetaData licensesMetaData = newState.metaData().custom(LicensesMetaData.TYPE);
@ -376,7 +357,7 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
.issuedTo(clusterService.getClusterName().value()) .issuedTo(clusterService.getClusterName().value())
.maxNodes(trialLicenseMaxNodes) .maxNodes(trialLicenseMaxNodes)
.issueDate(issueDate) .issueDate(issueDate)
.expiryDate(issueDate + trialLicenseDuration.getMillis()); .expiryDate(issueDate + TRIAL_LICENSE_DURATION.getMillis());
License trialLicense = TrialLicense.create(specBuilder); License trialLicense = TrialLicense.create(specBuilder);
mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(trialLicense)); mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(trialLicense));
return ClusterState.builder(currentState).metaData(mdBuilder).build(); return ClusterState.builder(currentState).metaData(mdBuilder).build();
@ -411,7 +392,6 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
@Override @Override
protected void doClose() throws ElasticsearchException { protected void doClose() throws ElasticsearchException {
transportService.removeHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME);
} }
/** /**
@ -440,9 +420,10 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
} }
// auto-generate license if no licenses ever existed // auto-generate license if no licenses ever existed
// this will trigger a subsequent cluster changed event // this will trigger a subsequent cluster changed event
if (prevLicensesMetaData == null if (currentClusterState.getNodes().isLocalNodeElectedMaster() &&
&& (currentLicensesMetaData == null || currentLicensesMetaData.getLicense() == null)) { prevLicensesMetaData == null &&
requestTrialLicense(currentClusterState); (currentLicensesMetaData == null || currentLicensesMetaData.getLicense() == null)) {
registerTrialLicense();
} }
} else if (logger.isDebugEnabled()) { } else if (logger.isDebugEnabled()) {
logger.debug("skipped license notifications reason: [{}]", GatewayService.STATE_NOT_RECOVERED_BLOCK); logger.debug("skipped license notifications reason: [{}]", GatewayService.STATE_NOT_RECOVERED_BLOCK);
@ -535,25 +516,17 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
&& clusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK) == false && clusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK) == false
&& clusterState.nodes().getMasterNode() != null) { && clusterState.nodes().getMasterNode() != null) {
final LicensesMetaData currentMetaData = clusterState.metaData().custom(LicensesMetaData.TYPE); final LicensesMetaData currentMetaData = clusterState.metaData().custom(LicensesMetaData.TYPE);
if (currentMetaData == null || currentMetaData.getLicense() == null) { if (clusterState.getNodes().isLocalNodeElectedMaster() &&
(currentMetaData == null || currentMetaData.getLicense() == null)) {
// triggers a cluster changed event // triggers a cluster changed event
// eventually notifying the current licensee // eventually notifying the current licensee
requestTrialLicense(clusterState); registerTrialLicense();
} else if (lifecycleState() == Lifecycle.State.STARTED) { } else if (lifecycleState() == Lifecycle.State.STARTED) {
notifyLicensees(currentMetaData.getLicense()); notifyLicensees(currentMetaData.getLicense());
} }
} }
} }
private void requestTrialLicense(final ClusterState currentState) {
DiscoveryNode masterNode = currentState.nodes().getMasterNode();
if (masterNode == null) {
throw new IllegalStateException("master not available when registering auto-generated license");
}
transportService.sendRequest(masterNode,
REGISTER_TRIAL_LICENSE_ACTION_NAME, TransportRequest.Empty.INSTANCE, EmptyTransportResponseHandler.INSTANCE_SAME);
}
License getLicense(final LicensesMetaData metaData) { License getLicense(final LicensesMetaData metaData) {
if (metaData != null) { if (metaData != null) {
License license = metaData.getLicense(); License license = metaData.getLicense();
@ -623,16 +596,4 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
} }
} }
} }
/**
* Request handler for trial license generation to master
*/
private class RegisterTrialLicenseRequestHandler implements TransportRequestHandler<TransportRequest.Empty> {
@Override
public void messageReceived(TransportRequest.Empty empty, TransportChannel channel) throws Exception {
registerTrialLicense();
channel.sendResponse(TransportResponse.Empty.INSTANCE);
}
}
} }

View File

@ -6,6 +6,7 @@
package org.elasticsearch.license.plugin.core; package org.elasticsearch.license.plugin.core;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.block.ClusterBlocks;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
@ -17,8 +18,6 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.LocalTransportAddress; import org.elasticsearch.common.transport.LocalTransportAddress;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.scheduler.SchedulerEngine;
import org.elasticsearch.xpack.support.clock.ClockMock; import org.elasticsearch.xpack.support.clock.ClockMock;
import org.junit.Before; import org.junit.Before;
@ -31,15 +30,15 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase {
protected LicensesService licensesService; protected LicensesService licensesService;
protected ClusterService clusterService; protected ClusterService clusterService;
protected TransportService transportService;
protected ClockMock clock; protected ClockMock clock;
protected DiscoveryNodes discoveryNodes;
@Before @Before
public void init() throws Exception { public void init() throws Exception {
clusterService = mock(ClusterService.class); clusterService = mock(ClusterService.class);
transportService = mock(TransportService.class);
clock = new ClockMock(); clock = new ClockMock();
licensesService = new LicensesService(Settings.EMPTY, clusterService, transportService, clock); licensesService = new LicensesService(Settings.EMPTY, clusterService, clock);
discoveryNodes = mock(DiscoveryNodes.class);
} }
protected void setInitialState(License license) { protected void setInitialState(License license) {
@ -49,11 +48,13 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase {
MetaData metaData = mock(MetaData.class); MetaData metaData = mock(MetaData.class);
when(metaData.custom(LicensesMetaData.TYPE)).thenReturn(new LicensesMetaData(license)); when(metaData.custom(LicensesMetaData.TYPE)).thenReturn(new LicensesMetaData(license));
when(state.metaData()).thenReturn(metaData); when(state.metaData()).thenReturn(metaData);
final DiscoveryNodes discoveryNodes = mock(DiscoveryNodes.class);
final DiscoveryNode mockNode = new DiscoveryNode("b", LocalTransportAddress.buildUnique(), emptyMap(), emptySet(), Version.CURRENT); final DiscoveryNode mockNode = new DiscoveryNode("b", LocalTransportAddress.buildUnique(), emptyMap(), emptySet(), Version.CURRENT);
when(discoveryNodes.getMasterNode()).thenReturn(mockNode); when(discoveryNodes.getMasterNode()).thenReturn(mockNode);
when(discoveryNodes.isLocalNodeElectedMaster()).thenReturn(false);
when(state.nodes()).thenReturn(discoveryNodes); when(state.nodes()).thenReturn(discoveryNodes);
when(state.getNodes()).thenReturn(discoveryNodes); // it is really ridiculous we have nodes() and getNodes()...
when(clusterService.state()).thenReturn(state); when(clusterService.state()).thenReturn(state);
when(clusterService.lifecycleState()).thenReturn(Lifecycle.State.STARTED); when(clusterService.lifecycleState()).thenReturn(Lifecycle.State.STARTED);
when(clusterService.getClusterName()).thenReturn(new ClusterName("a"));
} }
} }

View File

@ -9,6 +9,7 @@ import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateUpdateTask;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.node.DiscoveryNodes;
@ -16,18 +17,17 @@ import org.elasticsearch.common.transport.LocalTransportAddress;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.TestUtils; import org.elasticsearch.license.plugin.TestUtils;
import org.elasticsearch.transport.EmptyTransportResponseHandler;
import org.elasticsearch.transport.TransportRequest;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.mockito.ArgumentCaptor;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet; import static java.util.Collections.emptySet;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase { public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase {
@ -70,11 +70,16 @@ public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase {
DiscoveryNode master = new DiscoveryNode("b", LocalTransportAddress.buildUnique(), emptyMap(), emptySet(), Version.CURRENT); DiscoveryNode master = new DiscoveryNode("b", LocalTransportAddress.buildUnique(), emptyMap(), emptySet(), Version.CURRENT);
ClusterState oldState = ClusterState.builder(new ClusterName("a")) ClusterState oldState = ClusterState.builder(new ClusterName("a"))
.nodes(DiscoveryNodes.builder().masterNodeId(master.getId()).put(master)).build(); .nodes(DiscoveryNodes.builder().masterNodeId(master.getId()).put(master)).build();
ClusterState newState = ClusterState.builder(oldState).build(); when(discoveryNodes.isLocalNodeElectedMaster()).thenReturn(true);
ClusterState newState = ClusterState.builder(oldState).nodes(discoveryNodes).build();
licensesService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState)); licensesService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState));
verify(transportService, times(2)) ArgumentCaptor<ClusterStateUpdateTask> stateUpdater = ArgumentCaptor.forClass(ClusterStateUpdateTask.class);
.sendRequest(any(DiscoveryNode.class), verify(clusterService, times(1)).submitStateUpdateTask(any(), stateUpdater.capture());
eq(LicensesService.REGISTER_TRIAL_LICENSE_ACTION_NAME), ClusterState stateWithLicense = stateUpdater.getValue().execute(newState);
any(TransportRequest.Empty.class), any(EmptyTransportResponseHandler.class)); LicensesMetaData licenseMetaData = stateWithLicense.metaData().custom(LicensesMetaData.TYPE);
assertNotNull(licenseMetaData);
assertNotNull(licenseMetaData.getLicense());
assertEquals(clock.millis() + LicensesService.TRIAL_LICENSE_DURATION.millis(), licenseMetaData.getLicense().expiryDate());
} }
} }

View File

@ -5,31 +5,39 @@
*/ */
package org.elasticsearch.license.plugin.core; package org.elasticsearch.license.plugin.core;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateUpdateTask;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.plugin.TestUtils; import org.elasticsearch.license.plugin.TestUtils;
import org.elasticsearch.transport.EmptyTransportResponseHandler; import org.mockito.ArgumentCaptor;
import org.elasticsearch.transport.TransportRequest; import org.mockito.Mockito;
import static org.elasticsearch.mock.orig.Mockito.times;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase { public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase {
public void testTrialLicenseRequestOnEmptyLicenseState() throws Exception { public void testTrialLicenseRequestOnEmptyLicenseState() throws Exception {
setInitialState(null); setInitialState(null);
when(discoveryNodes.isLocalNodeElectedMaster()).thenReturn(true);
TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee( TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee(
"testTrialLicenseRequestOnEmptyLicenseState", logger); "testTrialLicenseRequestOnEmptyLicenseState", logger);
licensesService.start(); licensesService.start();
licensesService.register(licensee); licensesService.register(licensee);
verify(transportService, times(1))
.sendRequest(any(DiscoveryNode.class), ClusterState state = ClusterState.builder(new ClusterName("a")).build();
eq(LicensesService.REGISTER_TRIAL_LICENSE_ACTION_NAME), ArgumentCaptor<ClusterStateUpdateTask> stateUpdater = ArgumentCaptor.forClass(ClusterStateUpdateTask.class);
any(TransportRequest.Empty.class), any(EmptyTransportResponseHandler.class)); verify(clusterService, Mockito.times(1)).submitStateUpdateTask(any(), stateUpdater.capture());
assertThat(licensee.statuses.size(), equalTo(0)); ClusterState stateWithLicense = stateUpdater.getValue().execute(state);
LicensesMetaData licenseMetaData = stateWithLicense.metaData().custom(LicensesMetaData.TYPE);
assertNotNull(licenseMetaData);
assertNotNull(licenseMetaData.getLicense());
assertEquals(clock.millis() + LicensesService.TRIAL_LICENSE_DURATION.millis(), licenseMetaData.getLicense().expiryDate());
licensesService.stop(); licensesService.stop();
} }

View File

@ -5,6 +5,10 @@
*/ */
package org.elasticsearch.license.plugin.core; package org.elasticsearch.license.plugin.core;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.ClusterStateUpdateTask;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
@ -13,10 +17,6 @@ import org.elasticsearch.license.plugin.TestUtils;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense; import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;

View File

@ -106,5 +106,4 @@ internal:indices/flush/synced/in_flight
internal:indices/flush/synced/pre internal:indices/flush/synced/pre
internal:indices/flush/synced/sync internal:indices/flush/synced/sync
internal:admin/repository/verify internal:admin/repository/verify
internal:plugin/license/cluster/register_trial_license
internal:transport/handshake internal:transport/handshake