LicensesService: make sure feature registration happens only after first cluster changed event

Tests: added multiple plugin integration tests
Minor fixes

Original commit: elastic/x-pack-elasticsearch@d9a20b4a10
This commit is contained in:
Areek Zillur 2014-10-31 18:31:17 -04:00
parent 7d41146f38
commit dc8e3850e4
14 changed files with 419 additions and 161 deletions

View File

@ -289,7 +289,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
@Override
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
// Change to debug
logger.info("Processed Trial License registration");
logger.debug("Processed Trial License registration");
LicensesMetaData licensesMetaData = newState.metaData().custom(LicensesMetaData.TYPE);
logLicenseMetaDataStats("new", licensesMetaData);
}
@ -314,7 +314,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
@Override
public void onFailure(String source, @Nullable Throwable t) {
logger.info("LicensesService: " + source, t);
logger.debug("LicensesService: " + source, t);
}
private boolean checkTrialLicenseGenerationCondition(String feature, LicensesWrapper licensesWrapper) {
@ -344,7 +344,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
@Override
protected void doStart() throws ElasticsearchException {
//Change to debug
logger.info("Started LicensesService");
logger.debug("Started LicensesService");
clusterService.add(this);
}
@ -372,7 +372,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
@Override
protected void doClose() throws ElasticsearchException {
logger.info("Closing LicensesService");
logger.debug("Closing LicensesService");
transportService.removeHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME);
}
@ -400,13 +400,13 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
ListenerHolder pendingRegistrationLister;
while ((pendingRegistrationLister = pendingListeners.poll()) != null) {
boolean masterAvailable = registerListener(pendingRegistrationLister);
logger.info("trying to register pending listener for " + pendingRegistrationLister.feature + " masterAvailable: " + masterAvailable);
logger.debug("trying to register pending listener for " + pendingRegistrationLister.feature + " masterAvailable: " + masterAvailable);
if (!masterAvailable) {
// if the master is not available do not, break out of trying pendingListeners
pendingListeners.add(pendingRegistrationLister);
break;
} else {
logger.info("successfully registered listener for: " + pendingRegistrationLister.feature);
logger.debug("successfully registered listener for: " + pendingRegistrationLister.feature);
registeredListeners.add(pendingRegistrationLister);
// make sure to notify new registered feature
// notifications could have been scheduled for it before it was registered
@ -418,14 +418,14 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
// notify all interested plugins
// Change to debug
if (previousClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK) || addedNewRegisteredListener) {
logger.info("calling notifyFeaturesAndScheduleNotification from clusterChanged");
logger.debug("calling notifyFeaturesAndScheduleNotification from clusterChanged");
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
} else {
logger.info("calling notifyFeaturesAndScheduleNotificationIfNeeded from clusterChanged");
logger.debug("calling notifyFeaturesAndScheduleNotificationIfNeeded from clusterChanged");
notifyFeaturesAndScheduleNotificationIfNeeded(currentLicensesMetaData);
}
} else {
logger.info("clusterChanged: no action [has STATE_NOT_RECOVERED_BLOCK]");
logger.debug("clusterChanged: no action [has STATE_NOT_RECOVERED_BLOCK]");
}
}
@ -436,7 +436,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
private void notifyFeaturesAndScheduleNotificationIfNeeded(LicensesMetaData currentLicensesMetaData) {
final LicensesMetaData lastNotifiedLicensesMetaData = lastObservedLicensesState.get();
if (lastNotifiedLicensesMetaData != null && lastNotifiedLicensesMetaData.equals(currentLicensesMetaData)) {
logger.info("currentLicensesMetaData has been already notified on");
logger.debug("currentLicensesMetaData has been already notified on");
return;
}
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
@ -465,34 +465,11 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
*/
private long notifyFeatures(LicensesMetaData currentLicensesMetaData) {
long nextScheduleFrequency = -1l;
StringBuilder sb = new StringBuilder("Registered listeners: [ ");
for (ListenerHolder listenerHolder : registeredListeners) {
sb.append("( ");
sb.append("feature:");
sb.append(listenerHolder.feature);
sb.append(", ");
long expiryDate;
if ((expiryDate = expiryDateForFeature(listenerHolder.feature, currentLicensesMetaData)) != -1l) {
sb.append(" license expiry: ");
sb.append(expiryDate);
sb.append(", ");
}
long expiryDate = expiryDateForFeature(listenerHolder.feature, currentLicensesMetaData);
long expiryDuration = expiryDate - System.currentTimeMillis();
if (expiryDate == -1l) {
sb.append("no trial/signed license found");
sb.append(", ");
} else {
sb.append("license expires in: ");
sb.append(TimeValue.timeValueMillis(expiryDuration).toString());
sb.append(", ");
}
if (expiryDuration > 0l) {
sb.append("calling enableFeatureIfNeeded");
listenerHolder.enableFeatureIfNeeded();
if (nextScheduleFrequency == -1l) {
nextScheduleFrequency = expiryDuration;
@ -500,22 +477,32 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
nextScheduleFrequency = Math.min(expiryDuration, nextScheduleFrequency);
}
} else {
sb.append("calling disableFeatureIfNeeded");
listenerHolder.disableFeatureIfNeeded();
}
sb.append(" )");
if (logger.isDebugEnabled()) {
String status;
if (expiryDate != -1l) {
status = "license expires in : " + TimeValue.timeValueMillis(expiryDate - System.currentTimeMillis());
} else {
status = "no trial/signed license found";
}
if (expiryDuration > 0l) {
status += " action: enableFeatureIfNeeded";
} else {
status += " action: disableFeatureIfNeeded";
}
logger.debug(listenerHolder.toString() + "" + status);
}
}
sb.append("]");
// Change to debug
logger.info(sb.toString());
logLicenseMetaDataStats("Setting last observed metaData", currentLicensesMetaData);
lastObservedLicensesState.set(currentLicensesMetaData);
if (nextScheduleFrequency == -1l) {
logger.info("no need to schedule next notification");
logger.debug("no need to schedule next notification");
} else {
logger.info("next notification time: " + TimeValue.timeValueMillis(nextScheduleFrequency).toString());
logger.debug("next notification time: " + TimeValue.timeValueMillis(nextScheduleFrequency).toString());
}
return nextScheduleFrequency;
@ -523,9 +510,9 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
}
private void logLicenseMetaDataStats(String prefix, LicensesMetaData licensesMetaData) {
if (licensesMetaData != null) {
logger.info(prefix + " LicensesMetaData: signedLicenses: " + licensesMetaData.getSignatures().size() + " trialLicenses: " + licensesMetaData.getEncodedTrialLicenses().size());
logger.debug(prefix + " LicensesMetaData: signedLicenses: " + licensesMetaData.getSignatures().size() + " trialLicenses: " + licensesMetaData.getEncodedTrialLicenses().size());
} else {
logger.info(prefix + " LicensesMetaData: signedLicenses: 0 trialLicenses: 0");
logger.debug(prefix + " LicensesMetaData: signedLicenses: 0 trialLicenses: 0");
}
}
@ -535,14 +522,9 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
@Override
public void register(String feature, TrialLicenseOptions trialLicenseOptions, Listener listener) {
final ListenerHolder listenerHolder = new ListenerHolder(feature, trialLicenseOptions, listener);
if (registerListener(listenerHolder)) {
logger.info("successfully registered listener for: " + listenerHolder.feature);
registeredListeners.add(listenerHolder);
} else {
logger.info("add listener for: " + listenerHolder.feature + " to pending registration queue");
logger.debug("add listener for: " + listenerHolder.feature + " to pending registration queue");
pendingListeners.add(listenerHolder);
}
}
/**
* Notifies new feature listener if it already has a signed license
@ -555,10 +537,10 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
* or if there is a global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK})
*/
private boolean registerListener(final ListenerHolder listenerHolder) {
logger.info("Registering listener for " + listenerHolder.feature);
logger.debug("Registering listener for " + listenerHolder.feature);
ClusterState currentState = clusterService.state();
if (currentState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
logger.info("Store as pendingRegistration [cluster has NOT_RECOVERED_BLOCK]");
logger.debug("Store as pendingRegistration [cluster has NOT_RECOVERED_BLOCK]");
return false;
}
@ -571,17 +553,17 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
RegisterTrialLicenseRequest request = new RegisterTrialLicenseRequest(listenerHolder.feature,
options.duration, options.maxNodes);
if (currentState.nodes().localNodeMaster()) {
logger.info("Executing trial license request");
logger.debug("Executing trial license request");
registerTrialLicense(request);
} else {
DiscoveryNode masterNode = currentState.nodes().masterNode();
if (masterNode != null) {
logger.info("Sending trial license request to master");
logger.debug("Sending trial license request to master");
transportService.sendRequest(masterNode,
REGISTER_TRIAL_LICENSE_ACTION_NAME, request, EmptyTransportResponseHandler.INSTANCE_SAME);
} else {
// could not sent register trial license request to master
logger.info("Store as pendingRegistration [master not available yet]");
logger.debug("Store as pendingRegistration [master not available yet]");
return false;
}
}
@ -589,13 +571,13 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
// notify feature as clusterChangedEvent may not happen
// as no trial or signed license has been found for feature
// Change to debug
logger.info("Calling notifyFeaturesAndScheduleNotification [no trial license spec provided]");
logger.debug("Calling notifyFeaturesAndScheduleNotification [no trial license spec provided]");
notifyFeaturesAndScheduleNotification(currentMetaData);
}
} else {
// signed license already found for the new registered
// feature, notify feature on registration
logger.info("Calling notifyFeaturesAndScheduleNotification [signed/trial license available]");
logger.debug("Calling notifyFeaturesAndScheduleNotification [signed/trial license available]");
notifyFeaturesAndScheduleNotification(currentMetaData);
}
return true;
@ -605,11 +587,11 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
final Map<String, ESLicense> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
ESLicense featureLicense;
if ((featureLicense = effectiveLicenses.get(feature)) != null) {
logger.info("effective license for " + feature + " relative expiry: " +
logger.debug("effective license for " + feature + " relative expiry: " +
TimeValue.timeValueMillis(effectiveLicenses.get(feature).expiryDate() - System.currentTimeMillis()));
return featureLicense.expiryDate();
}
logger.info("no effective license for " + feature);
logger.debug("no effective license for " + feature);
return -1l;
}
@ -655,9 +637,9 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
try {
final TimeValue delay = TimeValue.timeValueMillis(nextScheduleDelay);
scheduledNotifications.add(threadPool.schedule(delay, executorName(), new LicensingClientNotificationJob()));
logger.info("Scheduling next notification after: " + delay);
logger.debug("Scheduling next notification after: " + delay);
} catch (EsRejectedExecutionException ex) {
logger.info("Couldn't re-schedule licensing client notification job", ex);
logger.debug("Couldn't re-schedule licensing client notification job", ex);
}
}
@ -673,7 +655,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
@Override
public void run() {
logger.info("Performing LicensingClientNotificationJob");
logger.debug("Performing LicensingClientNotificationJob");
// next clusterChanged event will deal with the missed notifications
ClusterState currentClusterState = clusterService.state();
@ -681,7 +663,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
LicensesMetaData currentLicensesMetaData = currentClusterState.metaData().custom(LicensesMetaData.TYPE);
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
} else {
logger.info("skip notification [STATE_NOT_RECOVERED_BLOCK]");
logger.debug("skip notification [STATE_NOT_RECOVERED_BLOCK]");
}
}
}
@ -744,6 +726,10 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
listener.onDisabled();
}
}
public String toString() {
return "(feature: " + feature + ", enabled: " + enabled.get() + ")";
}
}
/**

View File

@ -16,12 +16,15 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.licensor.ESLicenseSigner;
import org.elasticsearch.license.plugin.consumer.TestPluginService1;
import org.elasticsearch.license.plugin.consumer.TestPluginService2;
import org.elasticsearch.license.plugin.consumer.TestPluginServiceBase;
import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.license.plugin.core.LicensesMetaData;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.test.InternalTestCluster;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -110,18 +113,37 @@ public abstract class AbstractLicensesIntegrationTests extends ElasticsearchInte
}, 2, TimeUnit.SECONDS), equalTo(true));
}
protected void assertConsumerPluginDisableNotification(int timeoutInSec) throws InterruptedException {
assertConsumerPluginNotification(false, timeoutInSec);
}
protected void assertConsumerPluginEnableNotification(int timeoutInSec) throws InterruptedException {
assertConsumerPluginNotification(true, timeoutInSec);
protected void assertConsumerPlugin1DisableNotification(int timeoutInSec) throws InterruptedException {
assertConsumerPlugin1Notification(false, timeoutInSec);
}
protected void assertConsumerPluginNotification(final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
protected void assertConsumerPlugin1EnableNotification(int timeoutInSec) throws InterruptedException {
assertConsumerPlugin1Notification(true, timeoutInSec);
}
protected void assertConsumerPlugin2DisableNotification(int timeoutInSec) throws InterruptedException {
assertConsumerPlugin2Notification(false, timeoutInSec);
}
protected void assertConsumerPlugin2EnableNotification(int timeoutInSec) throws InterruptedException {
assertConsumerPlugin2Notification(true, timeoutInSec);
}
protected void assertConsumerPlugin2Notification(final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
final Iterable<TestPluginServiceBase> consumerPluginServices = consumerPlugin2Services();
assertConsumerPluginNotification(consumerPluginServices, expectedEnabled, timeoutInSec);
}
protected void assertConsumerPlugin1Notification(final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
final Iterable<TestPluginServiceBase> consumerPluginServices = consumerPlugin1Services();
assertConsumerPluginNotification(consumerPluginServices, expectedEnabled, timeoutInSec);
}
private void assertConsumerPluginNotification(final Iterable<TestPluginServiceBase> consumerPluginServices, final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
assertThat(awaitBusy(new Predicate<Object>() {
@Override
public boolean apply(Object o) {
for (TestPluginService pluginService : consumerPluginServices()) {
for (TestPluginServiceBase pluginService : consumerPluginServices) {
if (expectedEnabled != pluginService.enabled()) {
return false;
}
@ -129,11 +151,25 @@ public abstract class AbstractLicensesIntegrationTests extends ElasticsearchInte
return true;
}
}, timeoutInSec, TimeUnit.SECONDS), equalTo(true));
}
private Iterable<TestPluginService> consumerPluginServices() {
private Iterable<TestPluginServiceBase> consumerPlugin2Services() {
final InternalTestCluster clients = internalCluster();
return clients.getDataNodeInstances(TestPluginService.class);
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
for (TestPluginServiceBase service : clients.getDataNodeInstances(TestPluginService2.class)) {
consumerPluginServices.add(service);
}
return consumerPluginServices;
}
private Iterable<TestPluginServiceBase> consumerPlugin1Services() {
final InternalTestCluster clients = internalCluster();
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
for (TestPluginServiceBase service : clients.getDataNodeInstances(TestPluginService1.class)) {
consumerPluginServices.add(service);
}
return consumerPluginServices;
}
private Iterable<LicensesManagerService> licensesManagerServices() {

View File

@ -5,22 +5,19 @@
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.base.Predicate;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.license.plugin.consumer.TestConsumerPlugin1;
import org.elasticsearch.license.plugin.consumer.TestPluginService1;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.elasticsearch.test.InternalTestCluster;
import org.junit.After;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
@ -30,12 +27,13 @@ public class LicensesPluginIntegrationTests extends AbstractLicensesIntegrationT
private final int trialLicenseDurationInSeconds = 2;
@Override
private final String FEATURE_NAME = TestPluginService1.FEATURE_NAME;
protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put("test_consumer_plugin.trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
.put("plugin.types", LicensePlugin.class.getName() + "," + TestConsumerPlugin.class.getName())
.put(TestConsumerPlugin1.NAME + ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
.put("plugin.types", LicensePlugin.class.getName() + "," + TestConsumerPlugin1.class.getName())
.build();
}
@ -48,85 +46,79 @@ public class LicensesPluginIntegrationTests extends AbstractLicensesIntegrationT
public void testTrialLicenseAndSignedLicenseNotification() throws Exception {
logger.info(" --> trial license generated");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnableNotification(1);
assertConsumerPlugin1EnableNotification(1);
logger.info(" --> check trial license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
assertConsumerPluginDisableNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPlugin1DisableNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME);
logger.info(" --> put signed license");
ESLicense license = generateSignedLicense(TestPluginService.FEATURE_NAME, TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
ESLicense license = generateSignedLicense(FEATURE_NAME, TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
final PutLicenseResponse putLicenseResponse = new PutLicenseRequestBuilder(client().admin().cluster()).setLicense(Lists.newArrayList(license)).get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
logger.info(" --> check signed license enabled notification");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnableNotification(1);
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPlugin1EnableNotification(1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisableNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPlugin1DisableNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME);
}
@Test
public void testTrialLicenseNotification() throws Exception {
logger.info(" --> check onEnabled for trial license");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnableNotification(1);
assertConsumerPlugin1EnableNotification(1);
logger.info(" --> sleep for rest of trailLicense duration");
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
logger.info(" --> check trial license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisableNotification(trialLicenseDurationInSeconds);
assertLicenseManagerDisabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPlugin1DisableNotification(trialLicenseDurationInSeconds);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME);
}
@Test
public void testOverlappingTrialAndSignedLicenseNotification() throws Exception {
logger.info(" --> check onEnabled for trial license");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnableNotification(1);
assertConsumerPlugin1EnableNotification(1);
logger.info(" --> put signed license while trial license is in effect");
ESLicense license = generateSignedLicense(TestPluginService.FEATURE_NAME, TimeValue.timeValueSeconds(trialLicenseDurationInSeconds * 2));
ESLicense license = generateSignedLicense(FEATURE_NAME, TimeValue.timeValueSeconds(trialLicenseDurationInSeconds * 2));
final PutLicenseResponse putLicenseResponse = new PutLicenseRequestBuilder(client().admin().cluster()).setLicense(Lists.newArrayList(license)).get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
logger.info(" --> check signed license enabled notification");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnableNotification(1);
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPlugin1EnableNotification(1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
logger.info(" --> sleep for rest of trailLicense duration");
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
logger.info(" --> check consumer is still enabled [signed license]");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnableNotification(1);
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPlugin1EnableNotification(1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisableNotification(trialLicenseDurationInSeconds * 2 * 2);
assertLicenseManagerDisabledFeatureFor(TestPluginService.FEATURE_NAME);
}
private LicensesManagerService masterLicenseManagerService() {
final InternalTestCluster clients = internalCluster();
return clients.getInstance(LicensesManagerService.class, clients.getMasterName());
assertConsumerPlugin1DisableNotification(trialLicenseDurationInSeconds * 2 * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME);
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.consumer.TestConsumerPlugin1;
import org.elasticsearch.license.plugin.consumer.TestConsumerPlugin2;
import org.elasticsearch.license.plugin.consumer.TestPluginService1;
import org.elasticsearch.license.plugin.consumer.TestPluginService2;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.junit.After;
import org.junit.Test;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
@ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0)
public class LicensesPluginsIntegrationTests extends AbstractLicensesIntegrationTests {
private final int trialLicenseDurationInSeconds = 2;
private final String FEATURE_NAME_1 = TestPluginService1.FEATURE_NAME;
private final String FEATURE_NAME_2 = TestPluginService2.FEATURE_NAME;
protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put(TestConsumerPlugin1.NAME + ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
.put(TestConsumerPlugin2.NAME + ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
.putArray("plugin.types", LicensePlugin.class.getName(), TestConsumerPlugin1.class.getName(), TestConsumerPlugin2.class.getName())
.build();
}
@After
public void beforeTest() throws Exception {
wipeAllLicenses();
}
@Test
public void testMultipleConsumerPlugins() throws Exception {
logger.info(" --> trial license generated");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
// consumer plugin service should return enabled on all data nodes
assertConsumerPlugin1EnableNotification(1);
assertConsumerPlugin2EnableNotification(1);
logger.info(" --> check trial license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
assertConsumerPlugin1DisableNotification(trialLicenseDurationInSeconds * 2);
assertConsumerPlugin2DisableNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
logger.info(" --> put signed license");
ESLicense license1 = generateSignedLicense(FEATURE_NAME_1, TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
ESLicense license2 = generateSignedLicense(FEATURE_NAME_2, TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
final PutLicenseResponse putLicenseResponse = new PutLicenseRequestBuilder(client().admin().cluster()).setLicense(Lists.newArrayList(license1, license2)).get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
logger.info(" --> check signed license enabled notification");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPlugin1EnableNotification(1);
assertConsumerPlugin2EnableNotification(1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPlugin1DisableNotification(trialLicenseDurationInSeconds * 2);
assertConsumerPlugin2DisableNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
}
}

View File

@ -15,10 +15,11 @@ import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.consumer.TestConsumerPlugin1;
import org.elasticsearch.license.plugin.consumer.TestPluginService1;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.elasticsearch.node.internal.InternalNode;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.util.Arrays;
@ -29,9 +30,10 @@ import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0)
public class LicensesServiceClusterRestartTest extends AbstractLicensesIntegrationTests {
public class LicensesServiceClusterTest extends AbstractLicensesIntegrationTests {
private final String FEATURE_NAME = TestPluginService1.FEATURE_NAME;
@Override
protected Settings transportClientSettings() {
return super.transportClientSettings();
}
@ -48,8 +50,8 @@ public class LicensesServiceClusterRestartTest extends AbstractLicensesIntegrati
.put("plugins.load_classpath_plugins", false)
.put("node.data", true)
.put("format", "json")
.put("test_consumer_plugin.trial_license_duration_in_seconds", 5)
.putArray("plugin.types", LicensePlugin.class.getName(), TestConsumerPlugin.class.getName())
.put(TestConsumerPlugin1.NAME + ".trial_license_duration_in_seconds", 5)
.putArray("plugin.types", LicensePlugin.class.getName(), TestConsumerPlugin1.class.getName())
.put(InternalNode.HTTP_ENABLED, true);
}
@ -60,50 +62,58 @@ public class LicensesServiceClusterRestartTest extends AbstractLicensesIntegrati
@Test
public void testClusterRestart() throws Exception {
logger.info("--> starting 1 node");
int numNodes = randomIntBetween(1, 5);
logger.info("--> starting " + numNodes + " node(s)");
for (int i = 0; i < numNodes; i++) {
internalCluster().startNode();
}
ensureGreen();
logger.info("--> put signed license");
final List<ESLicense> esLicenses = generateAndPutLicense();
getAndCheckLicense(esLicenses);
logger.info("--> restart all nodes");
internalCluster().fullRestart();
ensureYellow();
logger.info("--> get and check signed license");
getAndCheckLicense(esLicenses);
}
@Test
public void testClusterNotRecovered() throws Exception {
logger.info("--> start 1 node (should not recover)");
internalCluster().startNode(nodeSettingsBuilder(0).put("gateway.recover_after_nodes", 3));
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPluginEnableNotification(1);
internalCluster().startNode(nodeSettingsBuilder(1).put("gateway.recover_after_nodes", 3));
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPluginEnableNotification(1);
logger.info("--> start first node (should not recover)");
internalCluster().startNode(nodeSettingsBuilder(0).put("gateway.recover_after_master_nodes", 2).put("node.master", true));
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
assertConsumerPlugin1EnableNotification(1);
internalCluster().startNode(nodeSettingsBuilder(2).put("gateway.recover_after_nodes", 3));
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPluginEnableNotification(1);
logger.info("--> start second node (should recover)");
internalCluster().startNode(nodeSettingsBuilder(1).put("gateway.recover_after_master_nodes", 2).put("node.master", true));
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
assertConsumerPlugin1EnableNotification(1);
internalCluster().stopRandomNonMasterNode();
// sleep for the entire trial license duration
assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPluginEnableNotification(1);
//internalCluster().startNode(nodeSettingsBuilder(2).put("gateway.expected_master_nodes", 3));
//assertLicenseManagerEnabledFeatureFor(TestPluginService.FEATURE_NAME);
//assertConsumerPlugin1EnableNotification(1);
logger.info("--> kill master node");
internalCluster().stopCurrentMasterNode();
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME);
assertConsumerPlugin1EnableNotification(1);
Thread.sleep(5 * 1050l);
internalCluster().startNode(nodeSettingsBuilder(3).put("gateway.recover_after_nodes", 3));
assertLicenseManagerDisabledFeatureFor(TestPluginService.FEATURE_NAME);
assertConsumerPluginDisableNotification(1);
internalCluster().startNode(nodeSettingsBuilder(3).put("gateway.recover_after_master_nodes", 2).put("node.master", true));
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME);
assertConsumerPlugin1DisableNotification(1);
}
private List<ESLicense> generateAndPutLicense() throws Exception {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
ESLicense license = generateSignedLicense("shield", TimeValue.timeValueMinutes(1));
ESLicense license = generateSignedLicense(FEATURE_NAME, TimeValue.timeValueMinutes(1));
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(cluster);
final List<ESLicense> putLicenses = Arrays.asList(license);
putLicenseRequestBuilder.setLicense(putLicenses);

View File

@ -8,6 +8,8 @@ package org.elasticsearch.license.plugin;
import org.elasticsearch.common.base.Predicate;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.consumer.TestConsumerPlugin1;
import org.elasticsearch.license.plugin.consumer.TestPluginService1;
import org.elasticsearch.node.internal.InternalNode;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.test.junit.annotations.TestLogging;
@ -27,8 +29,8 @@ public class LicensesServiceNodeTests extends AbstractLicensesIntegrationTests {
protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put("test_consumer_plugin.trial_license_duration_in_seconds", 10)
.putArray("plugin.types", LicensePlugin.class.getName(), TestConsumerPlugin.class.getName())
.put(TestConsumerPlugin1.NAME + ".trial_license_duration_in_seconds", 5)
.putArray("plugin.types", LicensePlugin.class.getName(), TestConsumerPlugin1.class.getName())
.put(InternalNode.HTTP_ENABLED, true)
.build();
}
@ -36,11 +38,11 @@ public class LicensesServiceNodeTests extends AbstractLicensesIntegrationTests {
@Test
@TestLogging("_root:DEBUG")
public void testPluginStatus() throws Exception {
final Iterable<TestPluginService> testPluginServices = internalCluster().getDataNodeInstances(TestPluginService.class);
final Iterable<TestPluginService1> testPluginServices = internalCluster().getDataNodeInstances(TestPluginService1.class);
assertThat(awaitBusy(new Predicate<Object>() {
@Override
public boolean apply(Object o) {
for (TestPluginService pluginService : testPluginServices) {
for (TestPluginService1 pluginService : testPluginServices) {
if (!pluginService.enabled()) {
return false;
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.license.plugin;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
@ -232,7 +233,7 @@ public class LicensesServiceTests extends AbstractLicensesIntegrationTests {
for (int i = 0; i < randomIntBetween(3, 10); i++) {
final TestTrackingClientListener clientListener = new TestTrackingClientListener();
String feature = randomRealisticUnicodeOfCodepointLengthBetween(2, 10);
expiryDuration = TimeValue.timeValueMillis(randomIntBetween(1, 5) * 1000l + expiryDuration.millis());
expiryDuration = TimeValue.timeValueMillis(randomIntBetween(1, 5) * 100l + expiryDuration.millis());
List<Action> actions = new ArrayList<>();
if (randomBoolean()) {
@ -309,6 +310,11 @@ public class LicensesServiceTests extends AbstractLicensesIntegrationTests {
public void run() {
clientService.register(feature, new LicensesService.TrialLicenseOptions(expiryDuration, 10),
clientListener);
// invoke clusterChanged event to flush out pendingRegistration
LicensesService licensesService = (LicensesService) clientService;
ClusterChangedEvent event = new ClusterChangedEvent("", clusterService().state(), clusterService().state());
licensesService.clusterChanged(event);
}
}, 0, 1, "should trigger onEnable once [trial license]");
}

View File

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.common.component.LifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
public class TestConsumerPlugin1 extends TestConsumerPluginBase {
public final static String NAME = "test_consumer_plugin_1";
@Inject
public TestConsumerPlugin1(Settings settings) {
super(settings);
}
@Override
protected Class<? extends LifecycleComponent> service() {
return TestPluginService1.class;
}
@Override
protected String pluginName() {
return NAME;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.common.component.LifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
public class TestConsumerPlugin2 extends TestConsumerPluginBase {
public static String NAME = "test_consumer_plugin_2";
@Inject
public TestConsumerPlugin2(Settings settings) {
super(settings);
}
@Override
protected Class<? extends LifecycleComponent> service() {
return TestPluginService2.class;
}
@Override
protected String pluginName() {
return NAME;
}
}

View File

@ -3,24 +3,22 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.component.LifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.plugins.AbstractPlugin;
import java.util.Collection;
public class TestConsumerPlugin extends AbstractPlugin {
public abstract class TestConsumerPluginBase extends AbstractPlugin {
private final boolean isEnabled;
@Inject
public TestConsumerPlugin(Settings settings) {
public TestConsumerPluginBase(Settings settings) {
if (DiscoveryNode.clientNode(settings)) {
// Enable plugin only on node clients
this.isEnabled = "node".equals(settings.get(Client.CLIENT_TYPE_SETTING));
@ -31,7 +29,7 @@ public class TestConsumerPlugin extends AbstractPlugin {
@Override
public String name() {
return "test_plugin";
return pluginName();
}
@Override
@ -44,8 +42,12 @@ public class TestConsumerPlugin extends AbstractPlugin {
public Collection<Class<? extends LifecycleComponent>> services() {
Collection<Class<? extends LifecycleComponent>> services = Lists.newArrayList();
if (isEnabled) {
services.add(TestPluginService.class);
services.add(service());
}
return services;
}
protected abstract Class<? extends LifecycleComponent> service();
protected abstract String pluginName();
}

View File

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicensesClientService;
@Singleton
public class TestPluginService1 extends TestPluginServiceBase {
public static String FEATURE_NAME = "feature1";
@Inject
public TestPluginService1(Settings settings, LicensesClientService licensesClientService) {
super(settings, licensesClientService);
}
@Override
public String featureName() {
return FEATURE_NAME;
}
@Override
public String settingPrefix() {
return TestConsumerPlugin1.NAME;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicensesClientService;
@Singleton
public class TestPluginService2 extends TestPluginServiceBase {
public static String FEATURE_NAME = "feature2";
@Inject
public TestPluginService2(Settings settings, LicensesClientService licensesClientService) {
super(settings, licensesClientService);
}
@Override
public String featureName() {
return FEATURE_NAME;
}
@Override
public String settingPrefix() {
return TestConsumerPlugin2.NAME;
}
}

View File

@ -3,12 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.plugin.core.LicensesClientService;
@ -16,27 +14,20 @@ import org.elasticsearch.license.plugin.core.LicensesService;
import java.util.concurrent.atomic.AtomicBoolean;
@Singleton
public class TestPluginService extends AbstractLifecycleComponent<TestPluginService> {
public abstract class TestPluginServiceBase extends AbstractLifecycleComponent<TestPluginServiceBase> {
private LicensesClientService licensesClientService;
// should be the same string used by the license Manger to generate
// signed license
static final String FEATURE_NAME = "shield";
// specify the trial license spec for the feature
// example: 30 day trial on 1000 nodes
final LicensesService.TrialLicenseOptions trialLicenseOptions;
private AtomicBoolean enabled = new AtomicBoolean(false);
@Inject
public TestPluginService(Settings settings, LicensesClientService licensesClientService) {
public TestPluginServiceBase(Settings settings, LicensesClientService licensesClientService) {
super(settings);
this.licensesClientService = licensesClientService;
int durationInSec = settings.getAsInt("test_consumer_plugin.trial_license_duration_in_seconds", -1);
logger.info("Trial license Duration in seconds: " + durationInSec);
int durationInSec = settings.getAsInt(settingPrefix() + ".trial_license_duration_in_seconds", -1);
if (durationInSec == -1) {
this.trialLicenseOptions = null;
} else {
@ -44,13 +35,19 @@ public class TestPluginService extends AbstractLifecycleComponent<TestPluginServ
}
}
// should be the same string used by the license Manger to generate
// signed license
public abstract String featureName();
public abstract String settingPrefix();
// check if feature is enabled
public boolean enabled() {
return enabled.get();
}
protected void doStart() throws ElasticsearchException {
licensesClientService.register(FEATURE_NAME,
licensesClientService.register(featureName(),
trialLicenseOptions,
new LicensingClientListener());
}

View File

@ -0,0 +1,15 @@
es.logger.level=DEBUG
log4j.rootLogger=${es.logger.level}, out
log4j.logger.org.apache.http=INFO, out
log4j.additivity.org.apache.http=false
log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n
log4j.logger.shield.audit.logfile=TRACE, access_log
log4j.appender.access_log=org.apache.log4j.ConsoleAppender
log4j.appender.access_log.layout=org.apache.log4j.PatternLayout
log4j.appender.access_log.layout.conversionPattern=%m%n