Merge pull request elastic/elasticsearch#2788 from rjernst/license1

Internal: Simplify creation of trial license

Original commit: elastic/x-pack-elasticsearch@1c92544b65
This commit is contained in:
Ryan Ernst 2016-07-12 13:44:21 -07:00 committed by GitHub
commit 8b2cdebf88
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