mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-03-25 01:19:02 +00:00
Merge pull request elastic/elasticsearch#2405 from areek/simplify_license_service_scheduling
Simplify license notification and scheduling Original commit: elastic/x-pack-elasticsearch@a77538c7d6
This commit is contained in:
commit
3874001751
@ -7,12 +7,15 @@ package org.elasticsearch.license.core;
|
||||
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.BytesRefIterator;
|
||||
import org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -72,4 +75,16 @@ public class LicenseVerifier {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verifyLicense(final License license) {
|
||||
final byte[] publicKeyBytes;
|
||||
try (InputStream is = LicenseVerifier.class.getResourceAsStream("/public.key")) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.copy(is, out);
|
||||
publicKeyBytes = out.toByteArray();
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return verifyLicense(license, publicKeyBytes);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -26,6 +27,10 @@ public class PutLicenseResponse extends AcknowledgedResponse implements ToXConte
|
||||
PutLicenseResponse() {
|
||||
}
|
||||
|
||||
public PutLicenseResponse(boolean acknowledged, LicensesStatus status) {
|
||||
this(acknowledged, status, null, Collections.<String, String[]>emptyMap());
|
||||
}
|
||||
|
||||
public PutLicenseResponse(boolean acknowledged, LicensesStatus status, String acknowledgeHeader,
|
||||
Map<String, String[]> acknowledgeMessages) {
|
||||
super(acknowledged);
|
||||
|
@ -20,8 +20,6 @@ import org.elasticsearch.license.plugin.core.LicensesService;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
|
||||
import static org.elasticsearch.license.plugin.core.LicensesService.LicensesUpdateResponse;
|
||||
|
||||
public class TransportPutLicenseAction extends TransportMasterNodeAction<PutLicenseRequest, PutLicenseResponse> {
|
||||
|
||||
private final LicensesService licensesService;
|
||||
@ -53,18 +51,7 @@ public class TransportPutLicenseAction extends TransportMasterNodeAction<PutLice
|
||||
@Override
|
||||
protected void masterOperation(final PutLicenseRequest request, ClusterState state, final ActionListener<PutLicenseResponse>
|
||||
listener) throws ElasticsearchException {
|
||||
licensesService.registerLicense(request, new ActionListener<LicensesUpdateResponse>() {
|
||||
@Override
|
||||
public void onResponse(LicensesUpdateResponse licensesUpdateResponse) {
|
||||
listener.onResponse(new PutLicenseResponse(licensesUpdateResponse.isAcknowledged(), licensesUpdateResponse.status(),
|
||||
licensesUpdateResponse.acknowledgementHeader(), licensesUpdateResponse.acknowledgeMessages()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
});
|
||||
licensesService.registerLicense(request, listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.common.logging.LoggerMessageFormat;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.xpack.scheduler.SchedulerEngine;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class ExpirationCallback {
|
||||
|
||||
static final String EXPIRATION_JOB_PREFIX = ".license_expiration_job_";
|
||||
|
||||
public enum Orientation {PRE, POST}
|
||||
|
||||
/**
|
||||
* Callback that is triggered every <code>frequency</code> when
|
||||
* current time is between <code>max</code> and <code>min</code>
|
||||
* before license expiry.
|
||||
*/
|
||||
public abstract static class Pre extends ExpirationCallback {
|
||||
|
||||
/**
|
||||
* Callback schedule prior to license expiry
|
||||
*
|
||||
* @param min latest relative time to execute before license expiry
|
||||
* @param max earliest relative time to execute before license expiry
|
||||
* @param frequency interval between execution
|
||||
*/
|
||||
public Pre(TimeValue min, TimeValue max, TimeValue frequency) {
|
||||
super(Orientation.PRE, min, max, frequency);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that is triggered every <code>frequency</code> when
|
||||
* current time is between <code>min</code> and <code>max</code>
|
||||
* after license expiry.
|
||||
*/
|
||||
public abstract static class Post extends ExpirationCallback {
|
||||
|
||||
/**
|
||||
* Callback schedule after license expiry
|
||||
*
|
||||
* @param min earliest relative time to execute after license expiry
|
||||
* @param max latest relative time to execute after license expiry
|
||||
* @param frequency interval between execution
|
||||
*/
|
||||
public Post(TimeValue min, TimeValue max, TimeValue frequency) {
|
||||
super(Orientation.POST, min, max, frequency);
|
||||
}
|
||||
}
|
||||
|
||||
private final String id;
|
||||
private final Orientation orientation;
|
||||
private final long min;
|
||||
private final long max;
|
||||
private final long frequency;
|
||||
|
||||
private ExpirationCallback(Orientation orientation, TimeValue min, TimeValue max, TimeValue frequency) {
|
||||
this.orientation = orientation;
|
||||
this.min = (min == null) ? 0 : min.getMillis();
|
||||
this.max = (max == null) ? Long.MAX_VALUE : max.getMillis();
|
||||
this.frequency = frequency.getMillis();
|
||||
this.id = String.join("", EXPIRATION_JOB_PREFIX, UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public final String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public final long getFrequency() {
|
||||
return frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the delay for the next trigger time. When <code>now</code> is in a
|
||||
* valid time bracket with respect to <code>expirationDate</code>, the delay is 0.
|
||||
* When <code>now</code> is before the time bracket, than delay to the start of the
|
||||
* time bracket and when <code>now</code> is passed the valid time bracket, the delay
|
||||
* is <code>null</code>
|
||||
* @param expirationDate license expiry date in milliseconds
|
||||
* @param now current time in milliseconds
|
||||
* @return time delay
|
||||
*/
|
||||
final TimeValue delay(long expirationDate, long now) {
|
||||
final TimeValue delay;
|
||||
switch (orientation) {
|
||||
case PRE:
|
||||
if (expirationDate >= now) {
|
||||
// license not yet expired
|
||||
long preExpiryDuration = expirationDate - now;
|
||||
if (preExpiryDuration > max) {
|
||||
// license duration is longer than maximum duration, delay it to the first match time
|
||||
delay = TimeValue.timeValueMillis(preExpiryDuration - max);
|
||||
} else if (preExpiryDuration <= max && preExpiryDuration >= min) {
|
||||
// no delay in valid time bracket
|
||||
delay = TimeValue.timeValueMillis(0);
|
||||
} else {
|
||||
// passed last match time
|
||||
delay = null;
|
||||
}
|
||||
} else {
|
||||
// invalid after license expiry
|
||||
delay = null;
|
||||
}
|
||||
break;
|
||||
case POST:
|
||||
if (expirationDate >= now) {
|
||||
// license not yet expired, delay it to the first match time
|
||||
delay = TimeValue.timeValueMillis(expirationDate - now + min);
|
||||
} else {
|
||||
// license has expired
|
||||
long expiredDuration = now - expirationDate;
|
||||
if (expiredDuration < min) {
|
||||
// license expiry duration is shorter than minimum duration, delay it to the first match time
|
||||
delay = TimeValue.timeValueMillis(min - expiredDuration);
|
||||
} else if (expiredDuration >= min && expiredDuration <= max) {
|
||||
// no delay in valid time bracket
|
||||
delay = TimeValue.timeValueMillis(0);
|
||||
} else {
|
||||
// passed last match time
|
||||
delay = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("orientation [" + orientation + "] unknown");
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SchedulerEngine.Schedule#nextScheduledTimeAfter(long, long)} with respect to
|
||||
* license expiry date
|
||||
*/
|
||||
public final long nextScheduledTimeForExpiry(long expiryDate, long startTime, long time) {
|
||||
TimeValue delay = delay(expiryDate, time);
|
||||
if (delay != null) {
|
||||
long delayInMillis = delay.getMillis();
|
||||
if (delayInMillis == 0L) {
|
||||
if (startTime == time) {
|
||||
// initial trigger and in time bracket, schedule immediately
|
||||
return time;
|
||||
} else {
|
||||
// in time bracket, add frequency
|
||||
return time + frequency;
|
||||
}
|
||||
} else {
|
||||
// not in time bracket
|
||||
return time + delayInMillis;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Code to execute when the expiry callback is triggered in a valid
|
||||
* time bracket
|
||||
* @param license license to operate on
|
||||
*/
|
||||
public abstract void on(License license);
|
||||
|
||||
public final String toString() {
|
||||
return LoggerMessageFormat.format(null, "ExpirationCallback:(orientation [{}], min [{}], max [{}], freq [{}])",
|
||||
orientation.name(), TimeValue.timeValueMillis(min), TimeValue.timeValueMillis(max),
|
||||
TimeValue.timeValueMillis(frequency));
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.xpack.scheduler.SchedulerEngine;
|
||||
|
||||
import static org.elasticsearch.license.plugin.core.LicensesService.GRACE_PERIOD_DURATION;
|
||||
import static org.elasticsearch.license.plugin.core.LicensesService.getLicenseState;
|
||||
|
||||
public class LicenseSchedule implements SchedulerEngine.Schedule {
|
||||
|
||||
private final License license;
|
||||
|
||||
LicenseSchedule(License license) {
|
||||
this.license = license;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextScheduledTimeAfter(long startTime, long time) {
|
||||
long nextScheduledTime = -1;
|
||||
switch (getLicenseState(license, time)) {
|
||||
case ENABLED:
|
||||
nextScheduledTime = license.expiryDate();
|
||||
break;
|
||||
case GRACE_PERIOD:
|
||||
nextScheduledTime = license.expiryDate() + GRACE_PERIOD_DURATION.getMillis();
|
||||
break;
|
||||
case DISABLED:
|
||||
if (license.issueDate() > time) {
|
||||
// when we encounter a license with a future issue date
|
||||
// which can happen with autogenerated license,
|
||||
// we want to schedule a notification on the license issue date
|
||||
// so the license is notificed once it is valid
|
||||
// see https://github.com/elastic/x-plugins/issues/983
|
||||
nextScheduledTime = license.issueDate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
return nextScheduledTime;
|
||||
}
|
||||
}
|
@ -12,9 +12,9 @@ import java.util.List;
|
||||
public interface LicensesManagerService {
|
||||
|
||||
/**
|
||||
* @return the id of registered licensees currently in <code>state</code>
|
||||
* @return current {@link LicenseState}
|
||||
*/
|
||||
List<String> licenseesWithState(LicenseState state);
|
||||
LicenseState licenseState();
|
||||
|
||||
/**
|
||||
* @return the currently active license, or {@code null} if no license is currently installed
|
||||
|
@ -20,20 +20,17 @@ import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.component.Lifecycle;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.inject.Singleton;
|
||||
import org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
||||
import org.elasticsearch.common.joda.Joda;
|
||||
import org.elasticsearch.common.logging.LoggerMessageFormat;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
|
||||
import org.elasticsearch.common.util.concurrent.FutureUtils;
|
||||
import org.elasticsearch.gateway.GatewayService;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.core.LicenseVerifier;
|
||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.EmptyTransportResponseHandler;
|
||||
import org.elasticsearch.transport.TransportChannel;
|
||||
@ -41,21 +38,17 @@ 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.support.clock.Clock;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
@ -76,18 +69,15 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
* <p>
|
||||
* All registered listeners are notified of the current license upon registration or when a new license is installed in the cluster state.
|
||||
* When a new license is notified as enabled to the registered listener, a notification is scheduled at the time of license expiry.
|
||||
* Registered listeners are notified using {@link #notifyAndSchedule(LicensesMetaData)}
|
||||
* Registered listeners are notified using {@link #onUpdate(LicensesMetaData)}
|
||||
*/
|
||||
@Singleton
|
||||
public class LicensesService extends AbstractLifecycleComponent implements ClusterStateListener, LicensesManagerService,
|
||||
LicenseeRegistry {
|
||||
LicenseeRegistry, SchedulerEngine.Listener {
|
||||
|
||||
public static final String REGISTER_TRIAL_LICENSE_ACTION_NAME = "internal:plugin/license/cluster/register_trial_license";
|
||||
|
||||
private final ClusterService clusterService;
|
||||
|
||||
private final ThreadPool threadPool;
|
||||
|
||||
private final TransportService transportService;
|
||||
|
||||
/**
|
||||
@ -95,20 +85,12 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
*/
|
||||
private final List<InternalLicensee> registeredLicensees = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* Currently active expiry notifications
|
||||
*/
|
||||
private final Queue<ScheduledFuture> expiryNotifications = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* Currently active event notifications for every registered listener
|
||||
*/
|
||||
private final Queue<ScheduledFuture> eventNotifications = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* Currently active license
|
||||
*/
|
||||
private final AtomicReference<License> currentLicense = new AtomicReference<>();
|
||||
private SchedulerEngine scheduler;
|
||||
private final Clock clock;
|
||||
|
||||
/**
|
||||
* Callbacks to notify relative to license expiry
|
||||
@ -128,7 +110,9 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
/**
|
||||
* Duration of grace period after a license has expired
|
||||
*/
|
||||
private TimeValue gracePeriodDuration = days(7);
|
||||
public static final TimeValue GRACE_PERIOD_DURATION = days(7);
|
||||
|
||||
private static final String LICENSE_JOB = "licenseJob";
|
||||
|
||||
private static final FormatDateTimeFormatter DATE_FORMATTER = Joda.forPattern("EEEE, MMMMM dd, yyyy", Locale.ROOT);
|
||||
|
||||
@ -136,16 +120,18 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
"please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:";
|
||||
|
||||
@Inject
|
||||
public LicensesService(Settings settings, ClusterService clusterService, ThreadPool threadPool, TransportService transportService) {
|
||||
public LicensesService(Settings settings, ClusterService clusterService, TransportService transportService, Clock clock) {
|
||||
super(settings);
|
||||
this.clusterService = clusterService;
|
||||
this.threadPool = threadPool;
|
||||
this.transportService = transportService;
|
||||
if (DiscoveryNode.isMasterNode(settings)) {
|
||||
transportService.registerRequestHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME, TransportRequest.Empty::new,
|
||||
ThreadPool.Names.SAME, new RegisterTrialLicenseRequestHandler());
|
||||
}
|
||||
populateExpirationCallbacks();
|
||||
this.clock = clock;
|
||||
this.scheduler = new SchedulerEngine(clock);
|
||||
this.scheduler.register(this);
|
||||
}
|
||||
|
||||
private void populateExpirationCallbacks() {
|
||||
@ -251,18 +237,17 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
* Registers new license in the cluster
|
||||
* Master only operation. Installs a new license on the master provided it is VALID
|
||||
*/
|
||||
public void registerLicense(final PutLicenseRequest request, final ActionListener<LicensesUpdateResponse> listener) {
|
||||
public void registerLicense(final PutLicenseRequest request, final ActionListener<PutLicenseResponse> listener) {
|
||||
final License newLicense = request.license();
|
||||
final long now = System.currentTimeMillis();
|
||||
if (!verifyLicense(newLicense) || newLicense.issueDate() > now) {
|
||||
listener.onResponse(new LicensesUpdateResponse(true, LicensesStatus.INVALID));
|
||||
final long now = clock.millis();
|
||||
if (!LicenseVerifier.verifyLicense(newLicense) || newLicense.issueDate() > now) {
|
||||
listener.onResponse(new PutLicenseResponse(true, LicensesStatus.INVALID));
|
||||
} else if (newLicense.expiryDate() < now) {
|
||||
listener.onResponse(new LicensesUpdateResponse(true, LicensesStatus.EXPIRED));
|
||||
listener.onResponse(new PutLicenseResponse(true, LicensesStatus.EXPIRED));
|
||||
} else {
|
||||
if (!request.acknowledged()) {
|
||||
final LicensesMetaData currentMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
|
||||
final License currentLicense = getLicense(currentMetaData);
|
||||
if (currentLicense != null && currentLicense != LicensesMetaData.LICENSE_TOMBSTONE) {
|
||||
final License currentLicense = getLicense();
|
||||
if (currentLicense != null) {
|
||||
Map<String, String[]> acknowledgeMessages = new HashMap<>(registeredLicensees.size() + 1);
|
||||
if (!License.isAutoGeneratedLicense(currentLicense.signature()) // current license is not auto-generated
|
||||
&& currentLicense.issueDate() > newLicense.issueDate()) { // and has a later issue date
|
||||
@ -278,72 +263,46 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
}
|
||||
if (!acknowledgeMessages.isEmpty()) {
|
||||
// needs acknowledgement
|
||||
listener.onResponse(new LicensesUpdateResponse(false, LicensesStatus.VALID, ACKNOWLEDGEMENT_HEADER,
|
||||
listener.onResponse(new PutLicenseResponse(false, LicensesStatus.VALID, ACKNOWLEDGEMENT_HEADER,
|
||||
acknowledgeMessages));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new
|
||||
AckedClusterStateUpdateTask<LicensesUpdateResponse>(request, listener) {
|
||||
@Override
|
||||
protected LicensesUpdateResponse newResponse(boolean acknowledged) {
|
||||
return new LicensesUpdateResponse(acknowledged, LicensesStatus.VALID);
|
||||
}
|
||||
AckedClusterStateUpdateTask<PutLicenseResponse>(request, listener) {
|
||||
@Override
|
||||
protected PutLicenseResponse newResponse(boolean acknowledged) {
|
||||
return new PutLicenseResponse(acknowledged, LicensesStatus.VALID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
||||
mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense));
|
||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
||||
mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense));
|
||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifyLicense(final License license) {
|
||||
final byte[] publicKeyBytes;
|
||||
try (InputStream is = LicensesService.class.getResourceAsStream("/public.key")) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.copy(is, out);
|
||||
publicKeyBytes = out.toByteArray();
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return LicenseVerifier.verifyLicense(license, publicKeyBytes);
|
||||
}
|
||||
|
||||
static TimeValue days(int days) {
|
||||
return TimeValue.timeValueHours(days * 24);
|
||||
}
|
||||
|
||||
public static class LicensesUpdateResponse extends ClusterStateUpdateResponse {
|
||||
private final LicensesStatus status;
|
||||
private final String acknowledgementHeader;
|
||||
private final Map<String, String[]> acknowledgeMessages;
|
||||
|
||||
public LicensesUpdateResponse(boolean acknowledged, LicensesStatus status) {
|
||||
this(acknowledged, status, null, Collections.<String, String[]>emptyMap());
|
||||
}
|
||||
|
||||
public LicensesUpdateResponse(boolean acknowledged, LicensesStatus status, String acknowledgementHeader,
|
||||
Map<String, String[]> acknowledgeMessages) {
|
||||
super(acknowledged);
|
||||
this.status = status;
|
||||
this.acknowledgeMessages = acknowledgeMessages;
|
||||
this.acknowledgementHeader = acknowledgementHeader;
|
||||
}
|
||||
|
||||
public LicensesStatus status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String acknowledgementHeader() {
|
||||
return acknowledgementHeader;
|
||||
}
|
||||
|
||||
public Map<String, String[]> acknowledgeMessages() {
|
||||
return acknowledgeMessages;
|
||||
@Override
|
||||
public void triggered(SchedulerEngine.Event event) {
|
||||
final LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
|
||||
if (licensesMetaData != null) {
|
||||
final License license = licensesMetaData.getLicense();
|
||||
if (event.getJobName().equals(LICENSE_JOB)) {
|
||||
notifyLicensees(license);
|
||||
} else if (event.getJobName().startsWith(ExpirationCallback.EXPIRATION_JOB_PREFIX)) {
|
||||
expirationCallbacks.stream()
|
||||
.filter(expirationCallback -> expirationCallback.getId().equals(event.getJobName()))
|
||||
.forEach(expirationCallback -> expirationCallback.on(license));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,35 +312,34 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
public void removeLicense(final DeleteLicenseRequest request, final ActionListener<ClusterStateUpdateResponse> listener) {
|
||||
clusterService.submitStateUpdateTask("delete license",
|
||||
new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(request, listener) {
|
||||
@Override
|
||||
protected ClusterStateUpdateResponse newResponse(boolean acknowledged) {
|
||||
return new ClusterStateUpdateResponse(acknowledged);
|
||||
}
|
||||
@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());
|
||||
mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(LicensesMetaData.LICENSE_TOMBSTONE));
|
||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
||||
} else {
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
});
|
||||
@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());
|
||||
mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(LicensesMetaData.LICENSE_TOMBSTONE));
|
||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
||||
} else {
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> licenseesWithState(LicenseState state) {
|
||||
List<String> licensees = new ArrayList<>(registeredLicensees.size());
|
||||
for (InternalLicensee licensee : registeredLicensees) {
|
||||
if (licensee.currentLicenseState == state) {
|
||||
licensees.add(licensee.id());
|
||||
}
|
||||
public LicenseState licenseState() {
|
||||
if (registeredLicensees.size() > 0) {
|
||||
return registeredLicensees.get(0).currentLicenseState;
|
||||
} else {
|
||||
final License license = getLicense(clusterService.state().metaData().custom(LicensesMetaData.TYPE));
|
||||
return getLicenseState(license, clock.millis());
|
||||
}
|
||||
return licensees;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -412,7 +370,7 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
||||
// do not generate a trial license if any license is present
|
||||
if (currentLicensesMetaData == null) {
|
||||
long issueDate = System.currentTimeMillis();
|
||||
long issueDate = clock.millis();
|
||||
License.Builder specBuilder = License.builder()
|
||||
.uid(UUID.randomUUID().toString())
|
||||
.issuedTo(clusterService.getClusterName().value())
|
||||
@ -437,26 +395,16 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
@Override
|
||||
protected void doStart() throws ElasticsearchException {
|
||||
clusterService.add(this);
|
||||
scheduler.start(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws ElasticsearchException {
|
||||
clusterService.remove(this);
|
||||
|
||||
// cancel all notifications
|
||||
for (ScheduledFuture scheduledNotification : expiryNotifications) {
|
||||
FutureUtils.cancel(scheduledNotification);
|
||||
}
|
||||
for (ScheduledFuture eventNotification : eventNotifications) {
|
||||
FutureUtils.cancel(eventNotification);
|
||||
}
|
||||
|
||||
scheduler.stop();
|
||||
// clear all handlers
|
||||
registeredLicensees.clear();
|
||||
|
||||
// empty out notification queue
|
||||
expiryNotifications.clear();
|
||||
|
||||
// clear current license
|
||||
currentLicense.set(null);
|
||||
}
|
||||
@ -482,16 +430,13 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
logger.debug("current [{}]", currentLicensesMetaData);
|
||||
}
|
||||
// notify all interested plugins
|
||||
if (previousClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
||||
notifyAndSchedule(currentLicensesMetaData);
|
||||
} else {
|
||||
if (prevLicensesMetaData == null) {
|
||||
if (currentLicensesMetaData != null) {
|
||||
notifyAndSchedule(currentLicensesMetaData);
|
||||
}
|
||||
} else if (!prevLicensesMetaData.equals(currentLicensesMetaData)) {
|
||||
notifyAndSchedule(currentLicensesMetaData);
|
||||
if (previousClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)
|
||||
|| prevLicensesMetaData == null) {
|
||||
if (currentLicensesMetaData != null) {
|
||||
onUpdate(currentLicensesMetaData);
|
||||
}
|
||||
} else if (!prevLicensesMetaData.equals(currentLicensesMetaData)) {
|
||||
onUpdate(currentLicensesMetaData);
|
||||
}
|
||||
// auto-generate license if no licenses ever existed
|
||||
// this will trigger a subsequent cluster changed event
|
||||
@ -504,245 +449,75 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies registered licensees of license state change and/or new active license
|
||||
* based on the license in <code>currentLicensesMetaData</code>.
|
||||
* Additionally schedules license expiry notifications and event callbacks
|
||||
* relative to the current license's expiry
|
||||
*/
|
||||
private void notifyAndSchedule(final LicensesMetaData currentLicensesMetaData) {
|
||||
final License license = getLicense(currentLicensesMetaData);
|
||||
private void notifyLicensees(final License license) {
|
||||
if (license == LicensesMetaData.LICENSE_TOMBSTONE) {
|
||||
// implies license has been explicitly deleted
|
||||
// update licensee states
|
||||
registeredLicensees.forEach(InternalLicensee::onRemove);
|
||||
return;
|
||||
}
|
||||
if (license != null) {
|
||||
logger.debug("notifying [{}] listeners", registeredLicensees.size());
|
||||
switch (getLicenseState(license, clock.millis())) {
|
||||
case ENABLED:
|
||||
for (InternalLicensee licensee : registeredLicensees) {
|
||||
licensee.onChange(license, LicenseState.ENABLED);
|
||||
}
|
||||
logger.debug("license [{}] - valid", license.uid());
|
||||
break;
|
||||
case GRACE_PERIOD:
|
||||
for (InternalLicensee licensee : registeredLicensees) {
|
||||
licensee.onChange(license, LicenseState.GRACE_PERIOD);
|
||||
}
|
||||
logger.warn("license [{}] - grace", license.uid());
|
||||
break;
|
||||
case DISABLED:
|
||||
for (InternalLicensee licensee : registeredLicensees) {
|
||||
licensee.onChange(license, LicenseState.DISABLED);
|
||||
}
|
||||
logger.warn("license [{}] - expired", license.uid());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static LicenseState getLicenseState(final License license, long time) {
|
||||
if (license == null) {
|
||||
return LicenseState.DISABLED;
|
||||
}
|
||||
if (license.issueDate() > time) {
|
||||
return LicenseState.DISABLED;
|
||||
}
|
||||
if (license.expiryDate() > time) {
|
||||
return LicenseState.ENABLED;
|
||||
}
|
||||
if ((license.expiryDate() + GRACE_PERIOD_DURATION.getMillis()) > time) {
|
||||
return LicenseState.GRACE_PERIOD;
|
||||
}
|
||||
return LicenseState.DISABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies registered licensees of license state change and/or new active license
|
||||
* based on the license in <code>currentLicensesMetaData</code>.
|
||||
* Additionally schedules license expiry notifications and event callbacks
|
||||
* relative to the current license's expiry
|
||||
*/
|
||||
void onUpdate(final LicensesMetaData currentLicensesMetaData) {
|
||||
final License license = getLicense(currentLicensesMetaData);
|
||||
// license can be null if the trial license is yet to be auto-generated
|
||||
// in this case, it is a no-op
|
||||
if (license != null) {
|
||||
logger.debug("notifying [{}] listeners", registeredLicensees.size());
|
||||
long now = System.currentTimeMillis();
|
||||
if (license.issueDate() > now) {
|
||||
logger.warn("license [{}] - invalid", license.uid());
|
||||
return;
|
||||
}
|
||||
long expiryDuration = license.expiryDate() - now;
|
||||
if (license.expiryDate() > now) {
|
||||
for (InternalLicensee licensee : registeredLicensees) {
|
||||
licensee.onChange(license, LicenseState.ENABLED);
|
||||
}
|
||||
logger.debug("license [{}] - valid", license.uid());
|
||||
final TimeValue delay = TimeValue.timeValueMillis(expiryDuration);
|
||||
// cancel any previous notifications
|
||||
cancelNotifications(expiryNotifications);
|
||||
try {
|
||||
logger.debug("schedule grace notification after [{}] for license [{}]", delay.toString(), license.uid());
|
||||
expiryNotifications.add(threadPool.schedule(delay, executorName(), new LicensingClientNotificationJob()));
|
||||
} catch (EsRejectedExecutionException ex) {
|
||||
logger.debug("couldn't schedule grace notification", ex);
|
||||
}
|
||||
} else if ((license.expiryDate() + gracePeriodDuration.getMillis()) > now) {
|
||||
for (InternalLicensee licensee : registeredLicensees) {
|
||||
licensee.onChange(license, LicenseState.GRACE_PERIOD);
|
||||
}
|
||||
logger.warn("license [{}] - grace", license.uid());
|
||||
final TimeValue delay = TimeValue.timeValueMillis(expiryDuration + gracePeriodDuration.getMillis());
|
||||
// cancel any previous notifications
|
||||
cancelNotifications(expiryNotifications);
|
||||
try {
|
||||
logger.debug("schedule expiry notification after [{}] for license [{}]", delay.toString(), license.uid());
|
||||
expiryNotifications.add(threadPool.schedule(delay, executorName(), new LicensingClientNotificationJob()));
|
||||
} catch (EsRejectedExecutionException ex) {
|
||||
logger.debug("couldn't schedule expiry notification", ex);
|
||||
}
|
||||
} else {
|
||||
for (InternalLicensee licensee : registeredLicensees) {
|
||||
licensee.onChange(license, LicenseState.DISABLED);
|
||||
}
|
||||
logger.warn("license [{}] - expired", license.uid());
|
||||
}
|
||||
if (!license.equals(currentLicense.get())) {
|
||||
notifyLicensees(license);
|
||||
if (license.equals(currentLicense.get()) == false) {
|
||||
currentLicense.set(license);
|
||||
// cancel all scheduled event notifications
|
||||
cancelNotifications(eventNotifications);
|
||||
// schedule expiry callbacks
|
||||
for (ExpirationCallback expirationCallback : this.expirationCallbacks) {
|
||||
final TimeValue delay;
|
||||
if (expirationCallback.matches(license.expiryDate(), now)) {
|
||||
expirationCallback.on(license);
|
||||
TimeValue frequency = expirationCallback.frequency();
|
||||
delay = frequency != null ? frequency : expirationCallback.delay(expiryDuration);
|
||||
} else {
|
||||
delay = expirationCallback.delay(expiryDuration);
|
||||
}
|
||||
if (delay != null) {
|
||||
eventNotifications.add(threadPool.schedule(delay, executorName(), new EventNotificationJob(expirationCallback)));
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("schedule [{}] after [{}]", expirationCallback, delay);
|
||||
}
|
||||
}
|
||||
logger.debug("scheduled expiry callbacks for [{}] expiring after [{}]", license.uid(),
|
||||
TimeValue.timeValueMillis(expiryDuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LicensingClientNotificationJob implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("running expiry notification");
|
||||
final ClusterState currentClusterState = clusterService.state();
|
||||
if (!currentClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
||||
final LicensesMetaData currentLicensesMetaData = currentClusterState.metaData().custom(LicensesMetaData.TYPE);
|
||||
notifyAndSchedule(currentLicensesMetaData);
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
// next clusterChanged event will deal with the missed notifications
|
||||
logger.debug("skip expiry notification [{}]", GatewayService.STATE_NOT_RECOVERED_BLOCK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EventNotificationJob implements Runnable {
|
||||
private final ExpirationCallback expirationCallback;
|
||||
|
||||
EventNotificationJob(ExpirationCallback expirationCallback) {
|
||||
this.expirationCallback = expirationCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("running event notification for [{}]", expirationCallback);
|
||||
LicensesMetaData currentLicensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
|
||||
License license = getLicense(currentLicensesMetaData);
|
||||
if (license != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (expirationCallback.matches(license.expiryDate(), now)) {
|
||||
expirationCallback.on(license);
|
||||
if (expirationCallback.frequency() != null) {
|
||||
// schedule next event
|
||||
eventNotifications.add(threadPool.schedule(expirationCallback.frequency(), executorName(), this));
|
||||
}
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
logger.debug("skip scheduling notification for [{}] with license expiring after [{}]", expirationCallback,
|
||||
TimeValue.timeValueMillis(license.expiryDate() - now));
|
||||
scheduler.add(new SchedulerEngine.Job(LICENSE_JOB, new LicenseSchedule(license)));
|
||||
for (ExpirationCallback expirationCallback : expirationCallbacks) {
|
||||
scheduler.add(new SchedulerEngine.Job(expirationCallback.getId(),
|
||||
(startTime, now) ->
|
||||
expirationCallback.nextScheduledTimeForExpiry(license.expiryDate(), startTime, now)));
|
||||
}
|
||||
}
|
||||
// clear out any finished event notifications
|
||||
while (!eventNotifications.isEmpty()) {
|
||||
ScheduledFuture notification = eventNotifications.peek();
|
||||
if (notification != null && notification.isDone()) {
|
||||
// remove the notifications that are done
|
||||
eventNotifications.poll();
|
||||
} else {
|
||||
// stop emptying out the queue as soon as the first undone future hits
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class ExpirationCallback {
|
||||
|
||||
public enum Orientation {PRE, POST}
|
||||
|
||||
public abstract static class Pre extends ExpirationCallback {
|
||||
|
||||
/**
|
||||
* Callback schedule prior to license expiry
|
||||
*
|
||||
* @param min latest relative time to execute before license expiry
|
||||
* @param max earliest relative time to execute before license expiry
|
||||
* @param frequency interval between execution
|
||||
*/
|
||||
public Pre(TimeValue min, TimeValue max, TimeValue frequency) {
|
||||
super(Orientation.PRE, min, max, frequency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(long expirationDate, long now) {
|
||||
long expiryDuration = expirationDate - now;
|
||||
if (expiryDuration > 0L) {
|
||||
if (expiryDuration <= max.getMillis()) {
|
||||
return expiryDuration >= min.getMillis();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeValue delay(long expiryDuration) {
|
||||
return TimeValue.timeValueMillis(expiryDuration - max.getMillis());
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class Post extends ExpirationCallback {
|
||||
|
||||
/**
|
||||
* Callback schedule after license expiry
|
||||
*
|
||||
* @param min earliest relative time to execute after license expiry
|
||||
* @param max latest relative time to execute after license expiry
|
||||
* @param frequency interval between execution
|
||||
*/
|
||||
public Post(TimeValue min, TimeValue max, TimeValue frequency) {
|
||||
super(Orientation.POST, min, max, frequency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(long expirationDate, long now) {
|
||||
long postExpiryDuration = now - expirationDate;
|
||||
if (postExpiryDuration > 0L) {
|
||||
if (postExpiryDuration <= max.getMillis()) {
|
||||
return postExpiryDuration >= min.getMillis();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeValue delay(long expiryDuration) {
|
||||
final long delay;
|
||||
if (expiryDuration >= 0L) {
|
||||
delay = expiryDuration + min.getMillis();
|
||||
} else {
|
||||
delay = (-1L * expiryDuration) - min.getMillis();
|
||||
}
|
||||
if (delay > 0L) {
|
||||
return TimeValue.timeValueMillis(delay);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Orientation orientation;
|
||||
protected final TimeValue min;
|
||||
protected final TimeValue max;
|
||||
private final TimeValue frequency;
|
||||
|
||||
private ExpirationCallback(Orientation orientation, TimeValue min, TimeValue max, TimeValue frequency) {
|
||||
this.orientation = orientation;
|
||||
this.min = (min == null) ? TimeValue.timeValueMillis(0) : min;
|
||||
this.max = (max == null) ? TimeValue.timeValueMillis(Long.MAX_VALUE) : max;
|
||||
this.frequency = frequency;
|
||||
}
|
||||
|
||||
public TimeValue frequency() {
|
||||
return frequency;
|
||||
}
|
||||
|
||||
public abstract TimeValue delay(long expiryDuration);
|
||||
|
||||
public abstract boolean matches(long expirationDate, long now);
|
||||
|
||||
public abstract void on(License license);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return LoggerMessageFormat.format(null, "ExpirationCallback:(orientation [{}], min [{}], max [{}], freq [{}])",
|
||||
orientation.name(), min, max, frequency);
|
||||
}
|
||||
}
|
||||
|
||||
@ -764,8 +539,8 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
// triggers a cluster changed event
|
||||
// eventually notifying the current licensee
|
||||
requestTrialLicense(clusterState);
|
||||
} else {
|
||||
notifyAndSchedule(currentMetaData);
|
||||
} else if (lifecycleState() == Lifecycle.State.STARTED) {
|
||||
notifyLicensees(currentMetaData.getLicense());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -787,7 +562,7 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
} else {
|
||||
boolean autoGeneratedLicense = License.isAutoGeneratedLicense(license.signature());
|
||||
if ((autoGeneratedLicense && TrialLicense.verify(license))
|
||||
|| (!autoGeneratedLicense && verifyLicense(license))) {
|
||||
|| (!autoGeneratedLicense && LicenseVerifier.verifyLicense(license))) {
|
||||
return license;
|
||||
}
|
||||
}
|
||||
@ -795,25 +570,6 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels out all notification futures
|
||||
*/
|
||||
private static void cancelNotifications(Queue<ScheduledFuture> scheduledNotifications) {
|
||||
// clear out notification queue
|
||||
while (!scheduledNotifications.isEmpty()) {
|
||||
ScheduledFuture notification = scheduledNotifications.peek();
|
||||
if (notification != null) {
|
||||
// cancel
|
||||
FutureUtils.cancel(notification);
|
||||
scheduledNotifications.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String executorName() {
|
||||
return ThreadPool.Names.GENERIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores acknowledgement, expiration and license notification callbacks
|
||||
* for a registered listener
|
||||
@ -879,19 +635,4 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
|
||||
channel.sendResponse(TransportResponse.Empty.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - temporary hack for tests, should be removed once we introduce `ClockMock`
|
||||
public void setGracePeriodDuration(TimeValue gracePeriodDuration) {
|
||||
this.gracePeriodDuration = gracePeriodDuration;
|
||||
}
|
||||
|
||||
// only for adding expiration callbacks for tests
|
||||
public void setExpirationCallbacks(List<ExpirationCallback> expirationCallbacks) {
|
||||
this.expirationCallbacks = expirationCallbacks;
|
||||
}
|
||||
|
||||
// TODO - temporary hack for tests, should be removed once we introduce `ClockMock`
|
||||
public void setTrialLicenseDuration(TimeValue trialLicenseDuration) {
|
||||
this.trialLicenseDuration = trialLicenseDuration;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.gateway.GatewayService;
|
||||
import org.elasticsearch.license.plugin.consumer.TestConsumerPluginBase;
|
||||
import org.elasticsearch.license.plugin.consumer.TestPluginServiceBase;
|
||||
import org.elasticsearch.license.plugin.core.LicenseState;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
||||
import org.elasticsearch.test.InternalTestCluster;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.junit.After;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
|
||||
|
||||
/**
|
||||
* Framework to test licensing plugin integration for existing/new consumer plugins
|
||||
* see {@link org.elasticsearch.license.plugin.LicensesEagerConsumerPluginIntegrationTests}
|
||||
* and {@link org.elasticsearch.license.plugin.LicensesLazyConsumerPluginIntegrationTests}
|
||||
* for example usage
|
||||
*/
|
||||
@ClusterScope(scope = TEST, numDataNodes = 2, numClientNodes = 0, transportClientRatio = 0.0)
|
||||
public abstract class AbstractLicensesConsumerPluginIntegrationTestCase extends AbstractLicensesIntegrationTestCase {
|
||||
protected final TestConsumerPluginBase consumerPlugin;
|
||||
|
||||
public AbstractLicensesConsumerPluginIntegrationTestCase(TestConsumerPluginBase consumerPlugin) {
|
||||
this.consumerPlugin = consumerPlugin;
|
||||
}
|
||||
|
||||
private final int trialLicenseDurationInSeconds = 20;
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return Settings.builder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
// this setting is only used in tests
|
||||
.put("_trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||
return Arrays.asList(XPackPlugin.class, consumerPlugin.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return nodePlugins();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() throws Exception {
|
||||
wipeAllLicenses();
|
||||
assertTrue(awaitBusy(() -> !clusterService().state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)));
|
||||
}
|
||||
|
||||
public void testTrialLicenseAndSignedLicenseNotification() throws Exception {
|
||||
logger.info("using {} consumer plugin", consumerPlugin.getClass().getName());
|
||||
logger.info(" --> trial license generated");
|
||||
// managerService should report feature to be enabled on all data nodes
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.ENABLED);
|
||||
// consumer plugin service should return enabled on all data nodes
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.ENABLED, 2);
|
||||
|
||||
logger.info(" --> check trial license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.GRACE_PERIOD, trialLicenseDurationInSeconds * 2);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.GRACE_PERIOD);
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.DISABLED, trialLicenseDurationInSeconds * 2);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.DISABLED);
|
||||
|
||||
logger.info(" --> put signed license");
|
||||
putLicense(TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
|
||||
|
||||
logger.info(" --> check signed license enabled notification");
|
||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.ENABLED, 1);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.ENABLED);
|
||||
|
||||
logger.info(" --> check signed license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.GRACE_PERIOD, trialLicenseDurationInSeconds * 2);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.GRACE_PERIOD);
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.DISABLED, trialLicenseDurationInSeconds * 2);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
public void testTrialLicenseNotification() throws Exception {
|
||||
logger.info(" --> check onEnabled for trial license");
|
||||
// managerService should report feature to be enabled on all data nodes
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.ENABLED);
|
||||
// consumer plugin service should return enabled on all data nodes
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.ENABLED, 1);
|
||||
|
||||
logger.info(" --> check trial license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.GRACE_PERIOD, trialLicenseDurationInSeconds);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.GRACE_PERIOD);
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.DISABLED, trialLicenseDurationInSeconds);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
public void testOverlappingTrialAndSignedLicenseNotification() throws Exception {
|
||||
logger.info(" --> check onEnabled for trial license");
|
||||
// managerService should report feature to be enabled on all data nodes
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.ENABLED);
|
||||
// consumer plugin service should return enabled on all data nodes
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.ENABLED, 1);
|
||||
|
||||
logger.info(" --> put signed license while trial license is in effect");
|
||||
putLicense(TimeValue.timeValueSeconds(trialLicenseDurationInSeconds * 2));
|
||||
|
||||
logger.info(" --> check signed license enabled notification");
|
||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.ENABLED, 1);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.ENABLED);
|
||||
|
||||
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)
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.ENABLED, 1);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.ENABLED);
|
||||
|
||||
logger.info(" --> check signed license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.GRACE_PERIOD, trialLicenseDurationInSeconds * 2 * 2);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.GRACE_PERIOD);
|
||||
assertConsumerPluginNotification(consumerPluginServices(), LicenseState.DISABLED, trialLicenseDurationInSeconds * 2 * 2);
|
||||
assertLicenseeState(consumerPlugin.id(), LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
private List<TestPluginServiceBase> consumerPluginServices() {
|
||||
final InternalTestCluster clients = internalCluster();
|
||||
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
|
||||
for (TestPluginServiceBase service : clients.getDataNodeInstances(consumerPlugin.service())) {
|
||||
consumerPluginServices.add(service);
|
||||
}
|
||||
return consumerPluginServices;
|
||||
}
|
||||
}
|
@ -11,37 +11,19 @@ import org.elasticsearch.cluster.ClusterStateUpdateTask;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.consumer.TestPluginServiceBase;
|
||||
import org.elasticsearch.license.plugin.core.LicenseState;
|
||||
import org.elasticsearch.license.plugin.core.LicensesManagerService;
|
||||
import org.elasticsearch.license.plugin.core.LicensesMetaData;
|
||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
||||
import org.elasticsearch.xpack.monitoring.Monitoring;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.xpack.security.Security;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
import org.elasticsearch.test.InternalTestCluster;
|
||||
import org.elasticsearch.xpack.watcher.Watcher;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.xpack.graph.Graph;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
|
||||
public abstract class AbstractLicensesIntegrationTestCase extends ESIntegTestCase {
|
||||
|
||||
@ -71,6 +53,30 @@ public abstract class AbstractLicensesIntegrationTestCase extends ESIntegTestCas
|
||||
return nodeSettings(0);
|
||||
}
|
||||
|
||||
protected void putLicense(final License license) throws InterruptedException {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
ClusterService clusterService = internalCluster().getInstance(ClusterService.class, internalCluster().getMasterName());
|
||||
clusterService.submitStateUpdateTask("putting license", new ClusterStateUpdateTask() {
|
||||
@Override
|
||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
||||
mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(license));
|
||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String source, @Nullable Exception e) {
|
||||
logger.error("error on metaData cleanup after test", e);
|
||||
}
|
||||
});
|
||||
latch.await();
|
||||
}
|
||||
|
||||
protected void wipeAllLicenses() throws InterruptedException {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
ClusterService clusterService = internalCluster().getInstance(ClusterService.class, internalCluster().getMasterName());
|
||||
@ -94,67 +100,4 @@ public abstract class AbstractLicensesIntegrationTestCase extends ESIntegTestCas
|
||||
});
|
||||
latch.await();
|
||||
}
|
||||
|
||||
protected void putLicense(TimeValue expiryDuration) throws Exception {
|
||||
License license1 = generateSignedLicense(expiryDuration);
|
||||
final PutLicenseResponse putLicenseResponse = new PutLicenseRequestBuilder(client().admin().cluster(),
|
||||
PutLicenseAction.INSTANCE).setLicense(license1).get();
|
||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
||||
}
|
||||
|
||||
protected void assertLicenseeState(final String id, final LicenseState state) throws InterruptedException {
|
||||
assertTrue("LicensesManagerService for licensee " + id + " should have status " + state.name(), awaitBusy(() -> {
|
||||
final InternalTestCluster clients = internalCluster();
|
||||
for (LicensesManagerService managerService : clients.getDataNodeInstances(LicensesManagerService.class)) {
|
||||
if (!managerService.licenseesWithState(state).contains(id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
protected void assertLazyConsumerPluginNotification(final LicenseState state, int timeoutInSec) throws InterruptedException {
|
||||
final List<TestPluginServiceBase> consumerPluginServices = consumerLazyPluginServices();
|
||||
assertConsumerPluginNotification(consumerPluginServices, state, timeoutInSec);
|
||||
}
|
||||
|
||||
protected void assertEagerConsumerPluginNotification(final LicenseState state, int timeoutInSec) throws InterruptedException {
|
||||
final List<TestPluginServiceBase> consumerPluginServices = consumerEagerPluginServices();
|
||||
assertConsumerPluginNotification(consumerPluginServices, state, timeoutInSec);
|
||||
}
|
||||
|
||||
protected void assertConsumerPluginNotification(final List<TestPluginServiceBase> consumerPluginServices, final LicenseState state,
|
||||
int timeoutInSec) throws InterruptedException {
|
||||
assertThat("At least one instance has to be present", consumerPluginServices.size(), greaterThan(0));
|
||||
boolean success = awaitBusy(() -> {
|
||||
for (TestPluginServiceBase pluginService : consumerPluginServices) {
|
||||
if (state != pluginService.state()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, timeoutInSec + 1, TimeUnit.SECONDS);
|
||||
logger.debug("Notification assertion complete");
|
||||
assertThat(consumerPluginServices.get(0).getClass().getName() + " should have status " + state.name(), success, equalTo(true));
|
||||
}
|
||||
|
||||
private List<TestPluginServiceBase> consumerLazyPluginServices() {
|
||||
final InternalTestCluster clients = internalCluster();
|
||||
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
|
||||
for (TestPluginServiceBase service : clients.getDataNodeInstances(LazyLicenseRegistrationPluginService.class)) {
|
||||
consumerPluginServices.add(service);
|
||||
}
|
||||
return consumerPluginServices;
|
||||
}
|
||||
|
||||
private List<TestPluginServiceBase> consumerEagerPluginServices() {
|
||||
final InternalTestCluster clients = internalCluster();
|
||||
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
|
||||
for (TestPluginServiceBase service : clients.getDataNodeInstances(EagerLicenseRegistrationPluginService.class)) {
|
||||
consumerPluginServices.add(service);
|
||||
}
|
||||
return consumerPluginServices;
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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.apache.lucene.util.LuceneTestCase.BadApple;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
||||
|
||||
// test is just too slow, please fix it to not be sleep-based
|
||||
@BadApple(bugUrl = "https://github.com/elastic/x-plugins/issues/1007")
|
||||
public class LicensesEagerConsumerPluginIntegrationTests extends AbstractLicensesConsumerPluginIntegrationTestCase {
|
||||
|
||||
public LicensesEagerConsumerPluginIntegrationTests() {
|
||||
super(new EagerLicenseRegistrationConsumerPlugin(Settings.EMPTY));
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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.apache.lucene.util.LuceneTestCase.BadApple;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
|
||||
|
||||
//test is just too slow, please fix it to not be sleep-based
|
||||
@BadApple(bugUrl = "https://github.com/elastic/x-plugins/issues/1007")
|
||||
public class LicensesLazyConsumerPluginIntegrationTests extends AbstractLicensesConsumerPluginIntegrationTestCase {
|
||||
|
||||
public LicensesLazyConsumerPluginIntegrationTests() {
|
||||
super(new LazyLicenseRegistrationConsumerPlugin(Settings.EMPTY));
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
/*
|
||||
* 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.apache.lucene.util.LuceneTestCase.BadApple;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.gateway.GatewayService;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
|
||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.core.LicenseState;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.junit.After;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
|
||||
|
||||
//test is just too slow, please fix it to not be sleep-based
|
||||
@BadApple(bugUrl = "https://github.com/elastic/x-plugins/issues/1007")
|
||||
@ClusterScope(scope = TEST, numDataNodes = 2, numClientNodes = 0)
|
||||
public class LicensesPluginIntegrationTests extends AbstractLicensesIntegrationTestCase {
|
||||
private final boolean useEagerLicenseRegistrationPlugin = randomBoolean();
|
||||
|
||||
private final int trialLicenseDurationInSeconds = 10;
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return Settings.builder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
// this setting is only used in tests
|
||||
.put("_trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||
if (useEagerLicenseRegistrationPlugin) {
|
||||
return Arrays.asList(XPackPlugin.class, EagerLicenseRegistrationConsumerPlugin.class);
|
||||
} else {
|
||||
return Arrays.asList(XPackPlugin.class, LazyLicenseRegistrationConsumerPlugin.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return nodePlugins();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() throws Exception {
|
||||
wipeAllLicenses();
|
||||
assertTrue(awaitBusy(() -> !clusterService().state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)));
|
||||
}
|
||||
|
||||
public void testTrialLicenseAndSignedLicenseNotification() throws Exception {
|
||||
logger.info("using {} consumer plugin", useEagerLicenseRegistrationPlugin ? "eager" : "lazy");
|
||||
logger.info(" --> trial license generated");
|
||||
// managerService should report feature to be enabled on all data nodes
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.ENABLED);
|
||||
// consumer plugin service should return enabled on all data nodes
|
||||
assertConsumerPluginEnabledNotification(2);
|
||||
|
||||
logger.info(" --> check trial license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
|
||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.GRACE_PERIOD);
|
||||
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.DISABLED);
|
||||
|
||||
logger.info(" --> put signed license");
|
||||
putLicense(TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
|
||||
|
||||
logger.info(" --> check signed license enabled notification");
|
||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
||||
assertConsumerPluginEnabledNotification(1);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.ENABLED);
|
||||
|
||||
logger.info(" --> check signed license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.GRACE_PERIOD);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
public void testTrialLicenseNotification() throws Exception {
|
||||
logger.info(" --> check onEnabled for trial license");
|
||||
// managerService should report feature to be enabled on all data nodes
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.ENABLED);
|
||||
// consumer plugin service should return enabled on all data nodes
|
||||
assertConsumerPluginEnabledNotification(1);
|
||||
|
||||
logger.info(" --> check trial license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.GRACE_PERIOD);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
public void testOverlappingTrialAndSignedLicenseNotification() throws Exception {
|
||||
logger.info(" --> check onEnabled for trial license");
|
||||
// managerService should report feature to be enabled on all data nodes
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.ENABLED);
|
||||
// consumer plugin service should return enabled on all data nodes
|
||||
assertConsumerPluginEnabledNotification(1);
|
||||
|
||||
logger.info(" --> put signed license while trial license is in effect");
|
||||
putLicense(TimeValue.timeValueSeconds(trialLicenseDurationInSeconds * 2));
|
||||
|
||||
logger.info(" --> check signed license enabled notification");
|
||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
||||
assertConsumerPluginEnabledNotification(1);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.ENABLED);
|
||||
|
||||
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)
|
||||
assertConsumerPluginEnabledNotification(1);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.ENABLED);
|
||||
|
||||
logger.info(" --> check signed license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2 * 2);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.GRACE_PERIOD);
|
||||
assertLicenseeState(getCurrentFeatureName(), LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
private String getCurrentFeatureName() {
|
||||
if (useEagerLicenseRegistrationPlugin) {
|
||||
return EagerLicenseRegistrationPluginService.ID;
|
||||
} else {
|
||||
return LazyLicenseRegistrationPluginService.ID;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertConsumerPluginEnabledNotification(int timeoutInSec) throws InterruptedException {
|
||||
if (useEagerLicenseRegistrationPlugin) {
|
||||
assertEagerConsumerPluginNotification(LicenseState.ENABLED, timeoutInSec);
|
||||
} else {
|
||||
assertLazyConsumerPluginNotification(LicenseState.ENABLED, timeoutInSec);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertConsumerPluginDisabledNotification(int timeoutInSec) throws InterruptedException {
|
||||
if (useEagerLicenseRegistrationPlugin) {
|
||||
assertEagerConsumerPluginNotification(LicenseState.GRACE_PERIOD, timeoutInSec);
|
||||
} else {
|
||||
assertLazyConsumerPluginNotification(LicenseState.GRACE_PERIOD, timeoutInSec);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
/*
|
||||
* 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.apache.lucene.util.LuceneTestCase.BadApple;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
|
||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.core.LicenseState;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.junit.After;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
|
||||
|
||||
//test is just too slow, please fix it to not be sleep-based
|
||||
@BadApple(bugUrl = "https://github.com/elastic/x-plugins/issues/1007")
|
||||
@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0)
|
||||
public class LicensesPluginsIntegrationTests extends AbstractLicensesIntegrationTestCase {
|
||||
|
||||
private static final String ID_1 = EagerLicenseRegistrationPluginService.ID;
|
||||
private static final String ID_2 = LazyLicenseRegistrationPluginService.ID;
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return Settings.builder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Settings nodeSettingsWithConsumerPlugin(int trialLicenseDuration) {
|
||||
return Settings.builder()
|
||||
.put(super.nodeSettings(0))
|
||||
// this setting is only used in tests
|
||||
.put("_trial_license_duration_in_seconds", trialLicenseDuration)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||
return Arrays.asList(XPackPlugin.class, EagerLicenseRegistrationConsumerPlugin.class, LazyLicenseRegistrationConsumerPlugin.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return nodePlugins();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() throws Exception {
|
||||
wipeAllLicenses();
|
||||
}
|
||||
|
||||
public void testMultipleConsumerPlugins() throws Exception {
|
||||
int nNodes = randomIntBetween(2, 3);
|
||||
int trialLicenseDurationInSec = 20;
|
||||
int signedLicenseDuration = 5;
|
||||
startNodesWithConsumerPlugins(nNodes, trialLicenseDurationInSec);
|
||||
|
||||
logger.info(" --> trial license generated");
|
||||
// managerService should report feature to be enabled on all data nodes
|
||||
assertLicenseeState(ID_1, LicenseState.ENABLED);
|
||||
assertLicenseeState(ID_2, LicenseState.ENABLED);
|
||||
// consumer plugin service should return enabled on all data nodes
|
||||
assertEagerConsumerPluginNotification(LicenseState.ENABLED, 1);
|
||||
assertLazyConsumerPluginNotification(LicenseState.ENABLED, 1);
|
||||
|
||||
logger.info(" --> check trial license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
|
||||
assertEagerConsumerPluginNotification(LicenseState.GRACE_PERIOD, trialLicenseDurationInSec * 2);
|
||||
assertLazyConsumerPluginNotification(LicenseState.GRACE_PERIOD, trialLicenseDurationInSec * 2);
|
||||
assertLicenseeState(ID_1, LicenseState.GRACE_PERIOD);
|
||||
assertLicenseeState(ID_2, LicenseState.GRACE_PERIOD);
|
||||
assertLicenseeState(ID_1, LicenseState.DISABLED);
|
||||
assertLicenseeState(ID_2, LicenseState.DISABLED);
|
||||
|
||||
logger.info(" --> put signed license");
|
||||
putLicense(TimeValue.timeValueSeconds(signedLicenseDuration));
|
||||
|
||||
logger.info(" --> check signed license enabled notification");
|
||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
||||
assertEagerConsumerPluginNotification(LicenseState.ENABLED, 1);
|
||||
assertLazyConsumerPluginNotification(LicenseState.ENABLED, 1);
|
||||
assertLicenseeState(ID_1, LicenseState.ENABLED);
|
||||
assertLicenseeState(ID_2, LicenseState.ENABLED);
|
||||
|
||||
logger.info(" --> check signed license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
||||
assertEagerConsumerPluginNotification(LicenseState.GRACE_PERIOD, signedLicenseDuration * 2);
|
||||
assertLazyConsumerPluginNotification(LicenseState.GRACE_PERIOD, signedLicenseDuration * 2);
|
||||
assertLicenseeState(ID_1, LicenseState.GRACE_PERIOD);
|
||||
assertLicenseeState(ID_2, LicenseState.GRACE_PERIOD);
|
||||
|
||||
assertEagerConsumerPluginNotification(LicenseState.DISABLED, 10 * 2);
|
||||
assertLazyConsumerPluginNotification(LicenseState.DISABLED, 10 * 2);
|
||||
assertLicenseeState(ID_1, LicenseState.DISABLED);
|
||||
assertLicenseeState(ID_2, LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
public void testRandomFeatureLicensesActions() throws Exception {
|
||||
int nNodes = randomIntBetween(2, 3);
|
||||
|
||||
startNodesWithConsumerPlugins(nNodes, 10);
|
||||
|
||||
logger.info(" --> check license enabled notification");
|
||||
assertEagerConsumerPluginNotification(LicenseState.ENABLED, 1);
|
||||
assertLazyConsumerPluginNotification(LicenseState.ENABLED, 1);
|
||||
assertLicenseeState(ID_1, LicenseState.ENABLED);
|
||||
assertLicenseeState(ID_2, LicenseState.ENABLED);
|
||||
|
||||
logger.info(" --> check license expiry notification");
|
||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
||||
assertEagerConsumerPluginNotification(LicenseState.GRACE_PERIOD, 10 * 2);
|
||||
assertLazyConsumerPluginNotification(LicenseState.GRACE_PERIOD, 10 * 2);
|
||||
assertLicenseeState(ID_1, LicenseState.GRACE_PERIOD);
|
||||
assertLicenseeState(ID_2, LicenseState.GRACE_PERIOD);
|
||||
assertEagerConsumerPluginNotification(LicenseState.DISABLED, 10 * 2);
|
||||
assertLazyConsumerPluginNotification(LicenseState.DISABLED, 10 * 2);
|
||||
assertLicenseeState(ID_1, LicenseState.DISABLED);
|
||||
assertLicenseeState(ID_2, LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
private void startNodesWithConsumerPlugins(int nNodes, int trialLicenseDuration) {
|
||||
for (int i = 0; i < nNodes; i++) {
|
||||
internalCluster().startNode(nodeSettingsWithConsumerPlugin(trialLicenseDuration));
|
||||
}
|
||||
}
|
||||
}
|
@ -19,20 +19,16 @@ import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
|
||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.core.LicenseState;
|
||||
import org.elasticsearch.license.plugin.core.LicensesMetaData;
|
||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
||||
import org.elasticsearch.node.Node;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
||||
import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
|
||||
@ -43,7 +39,6 @@ import static org.hamcrest.CoreMatchers.nullValue;
|
||||
|
||||
@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0, maxNumDataNodes = 0, transportClientRatio = 0)
|
||||
public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTestCase {
|
||||
private final String[] PLUGINS = {EagerLicenseRegistrationPluginService.ID, LazyLicenseRegistrationPluginService.ID};
|
||||
|
||||
@Override
|
||||
protected Settings transportClientSettings() {
|
||||
@ -59,16 +54,12 @@ public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTest
|
||||
return Settings.builder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
.put("node.data", true)
|
||||
// this setting is only used in tests
|
||||
.put("_trial_license_duration_in_seconds", 9)
|
||||
// this setting is only used in tests
|
||||
.put("_grace_duration_in_seconds", 9)
|
||||
.put(NetworkModule.HTTP_ENABLED.getKey(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||
return Arrays.asList(XPackPlugin.class, EagerLicenseRegistrationConsumerPlugin.class, LazyLicenseRegistrationConsumerPlugin.class);
|
||||
return Collections.singletonList(XPackPlugin.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -107,46 +98,57 @@ public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTest
|
||||
wipeAllLicenses();
|
||||
}
|
||||
|
||||
|
||||
private void assertLicenseState(LicenseState state) throws InterruptedException {
|
||||
boolean success = awaitBusy(() -> {
|
||||
for (LicensesService service : internalCluster().getDataNodeInstances(LicensesService.class)) {
|
||||
if (service.licenseState() == state) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
assertTrue(success);
|
||||
}
|
||||
|
||||
public void testClusterRestartWhileEnabled() throws Exception {
|
||||
wipeAllLicenses();
|
||||
internalCluster().startNode();
|
||||
ensureGreen();
|
||||
assertEagerConsumerPluginNotification(LicenseState.ENABLED, 5);
|
||||
assertLazyConsumerPluginNotification(LicenseState.ENABLED, 5);
|
||||
assertLicenseState(LicenseState.ENABLED);
|
||||
logger.info("--> restart node");
|
||||
internalCluster().fullRestart();
|
||||
ensureYellow();
|
||||
logger.info("--> await node for enabled");
|
||||
assertEagerConsumerPluginNotification(LicenseState.ENABLED, 5);
|
||||
assertLazyConsumerPluginNotification(LicenseState.ENABLED, 5);
|
||||
assertLicenseState(LicenseState.ENABLED);
|
||||
}
|
||||
|
||||
public void testClusterRestartWhileGrace() throws Exception {
|
||||
wipeAllLicenses();
|
||||
internalCluster().startNode();
|
||||
assertLicenseState(LicenseState.ENABLED);
|
||||
putLicense(TestUtils.generateSignedLicense(TimeValue.timeValueMillis(0)));
|
||||
ensureGreen();
|
||||
assertEagerConsumerPluginNotification(LicenseState.GRACE_PERIOD, 10);
|
||||
assertLazyConsumerPluginNotification(LicenseState.GRACE_PERIOD, 10);
|
||||
assertLicenseState(LicenseState.GRACE_PERIOD);
|
||||
logger.info("--> restart node");
|
||||
internalCluster().fullRestart();
|
||||
ensureYellow();
|
||||
logger.info("--> await node for grace_period");
|
||||
assertEagerConsumerPluginNotification(LicenseState.GRACE_PERIOD, 5);
|
||||
assertLazyConsumerPluginNotification(LicenseState.GRACE_PERIOD, 5);
|
||||
assertLicenseState(LicenseState.GRACE_PERIOD);
|
||||
}
|
||||
|
||||
public void testClusterRestartWhileExpired() throws Exception {
|
||||
wipeAllLicenses();
|
||||
internalCluster().startNode();
|
||||
ensureGreen();
|
||||
assertEagerConsumerPluginNotification(LicenseState.DISABLED, 20);
|
||||
assertLazyConsumerPluginNotification(LicenseState.DISABLED, 20);
|
||||
assertLicenseState(LicenseState.ENABLED);
|
||||
putLicense(TestUtils.generateExpiredLicense(System.currentTimeMillis() - LicensesService.GRACE_PERIOD_DURATION.getMillis()));
|
||||
assertLicenseState(LicenseState.DISABLED);
|
||||
logger.info("--> restart node");
|
||||
internalCluster().fullRestart();
|
||||
ensureYellow();
|
||||
logger.info("--> await node for disabled");
|
||||
assertEagerConsumerPluginNotification(LicenseState.DISABLED, 5);
|
||||
assertLazyConsumerPluginNotification(LicenseState.DISABLED, 5);
|
||||
assertLicenseState(LicenseState.DISABLED);
|
||||
}
|
||||
|
||||
public void testClusterNotRecovered() throws Exception {
|
||||
@ -154,27 +156,7 @@ public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTest
|
||||
internalCluster().startNode(nodeSettingsBuilder(0).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true));
|
||||
logger.info("--> start second master out of two [recovered state]");
|
||||
internalCluster().startNode(nodeSettingsBuilder(1).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true));
|
||||
assertLicenseesStateEnabled();
|
||||
assertConsumerPluginEnabledNotification(1);
|
||||
}
|
||||
|
||||
public void testAtMostOnceTrialLicenseGeneration() throws Exception {
|
||||
wipeAllLicenses();
|
||||
logger.info("--> start one node [trial license should be generated & enabled]");
|
||||
internalCluster().startNode(nodeSettingsBuilder(0));
|
||||
assertLicenseesStateEnabled();
|
||||
assertConsumerPluginEnabledNotification(1);
|
||||
|
||||
logger.info("--> start another node [trial license should be propagated from the old master not generated]");
|
||||
internalCluster().startNode(nodeSettings(1));
|
||||
assertLicenseesStateEnabled();
|
||||
assertConsumerPluginEnabledNotification(1);
|
||||
|
||||
logger.info("--> check if multiple trial licenses are found for a id");
|
||||
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
|
||||
assertThat(licensesMetaData.getLicense(), not(LicensesMetaData.LICENSE_TOMBSTONE));
|
||||
|
||||
wipeAllLicenses();
|
||||
assertLicenseState(LicenseState.ENABLED);
|
||||
}
|
||||
|
||||
private void removeLicense() throws Exception {
|
||||
@ -216,15 +198,4 @@ public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTest
|
||||
assertThat(licensesMetaData, notNullValue());
|
||||
assertThat(licensesMetaData.getLicense(), not(LicensesMetaData.LICENSE_TOMBSTONE));
|
||||
}
|
||||
|
||||
private void assertLicenseesStateEnabled() throws Exception {
|
||||
for (String id : PLUGINS) {
|
||||
assertLicenseeState(id, LicenseState.ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertConsumerPluginEnabledNotification(int timeoutInSec) throws InterruptedException {
|
||||
assertEagerConsumerPluginNotification(LicenseState.ENABLED, timeoutInSec);
|
||||
assertLazyConsumerPluginNotification(LicenseState.ENABLED, timeoutInSec);
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* 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.network.NetworkModule;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
||||
import org.elasticsearch.license.plugin.core.LicenseState;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
|
||||
|
||||
/**
|
||||
*/
|
||||
@ESIntegTestCase.ClusterScope(scope = TEST, supportsDedicatedMasters = false, numDataNodes = 10, numClientNodes = 0)
|
||||
public class LicensesServiceNodeTests extends AbstractLicensesIntegrationTestCase {
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return Settings.builder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
.put(NetworkModule.HTTP_ENABLED.getKey(), true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||
return Arrays.asList(XPackPlugin.class, EagerLicenseRegistrationConsumerPlugin.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return nodePlugins();
|
||||
}
|
||||
|
||||
public void testPluginStatus() throws Exception {
|
||||
final Iterable<EagerLicenseRegistrationPluginService> testPluginServices =
|
||||
internalCluster().getDataNodeInstances(EagerLicenseRegistrationPluginService.class);
|
||||
assertTrue(awaitBusy(() -> {
|
||||
for (EagerLicenseRegistrationPluginService pluginService : testPluginServices) {
|
||||
if (pluginService.state() != LicenseState.ENABLED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -31,6 +31,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.elasticsearch.license.plugin.TestUtils.dateMath;
|
||||
import static org.elasticsearch.license.plugin.TestUtils.generateExpiredLicense;
|
||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
@ -129,7 +130,7 @@ public class LicensesTransportTests extends ESSingleNodeTestCase {
|
||||
}
|
||||
|
||||
public void testPutExpiredLicense() throws Exception {
|
||||
License expiredLicense = generateSignedLicense(dateMath("now-10d/d", System.currentTimeMillis()), TimeValue.timeValueMinutes(2));
|
||||
License expiredLicense = generateExpiredLicense();
|
||||
PutLicenseRequestBuilder builder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE);
|
||||
builder.setLicense(expiredLicense);
|
||||
PutLicenseResponse putLicenseResponse = builder.get();
|
||||
@ -162,7 +163,7 @@ public class LicensesTransportTests extends ESSingleNodeTestCase {
|
||||
License goldLicense = generateSignedLicense("gold", TimeValue.timeValueMinutes(5));
|
||||
PutLicenseRequestBuilder putLicenseRequestBuilder =
|
||||
new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(goldLicense)
|
||||
.setAcknowledge(true);
|
||||
.setAcknowledge(true);
|
||||
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
||||
@ -177,4 +178,4 @@ public class LicensesTransportTests extends ESSingleNodeTestCase {
|
||||
getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
||||
assertNull(getLicenseResponse.license());
|
||||
}
|
||||
}
|
||||
}
|
@ -23,11 +23,13 @@ import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.licensor.LicenseSigner;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
||||
import org.elasticsearch.license.plugin.core.Licensee;
|
||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -90,7 +92,7 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
public static License generateSignedLicense(String type, long issueDate, TimeValue expiryDuration) throws Exception {
|
||||
long issue = (issueDate != -1L) ? issueDate : System.currentTimeMillis();
|
||||
long issue = (issueDate != -1L) ? issueDate : System.currentTimeMillis() - TimeValue.timeValueHours(2).getMillis();
|
||||
int version = randomIntBetween(License.VERSION_START, License.VERSION_CURRENT);
|
||||
final String licenseType;
|
||||
if (version < License.VERSION_NO_FEATURE_TYPE) {
|
||||
@ -101,7 +103,7 @@ public class TestUtils {
|
||||
final License.Builder builder = License.builder()
|
||||
.uid(UUID.randomUUID().toString())
|
||||
.version(version)
|
||||
.expiryDate(issue + expiryDuration.getMillis())
|
||||
.expiryDate(System.currentTimeMillis() + expiryDuration.getMillis())
|
||||
.issueDate(issue)
|
||||
.type(licenseType)
|
||||
.issuedTo("customer")
|
||||
@ -115,6 +117,24 @@ public class TestUtils {
|
||||
return signer.sign(builder.build());
|
||||
}
|
||||
|
||||
public static License generateExpiredLicense() throws Exception {
|
||||
return generateExpiredLicense(System.currentTimeMillis() - TimeValue.timeValueHours(randomIntBetween(1, 10)).getMillis());
|
||||
}
|
||||
|
||||
public static License generateExpiredLicense(long expiryDate) throws Exception {
|
||||
final License.Builder builder = License.builder()
|
||||
.uid(UUID.randomUUID().toString())
|
||||
.version(License.VERSION_CURRENT)
|
||||
.expiryDate(expiryDate)
|
||||
.issueDate(expiryDate - TimeValue.timeValueMinutes(10).getMillis())
|
||||
.type(randomFrom("basic", "silver", "dev", "gold", "platinum"))
|
||||
.issuedTo("customer")
|
||||
.issuer("elasticsearch")
|
||||
.maxNodes(5);
|
||||
LicenseSigner signer = new LicenseSigner(getTestPriKeyPath(), getTestPubKeyPath());
|
||||
return signer.sign(builder.build());
|
||||
}
|
||||
|
||||
private static Path getResourcePath(String resource) throws Exception {
|
||||
return PathUtils.get(TestUtils.class.getResource(resource).toURI());
|
||||
}
|
||||
@ -138,9 +158,9 @@ public class TestUtils {
|
||||
PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(license);
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicReference<LicensesStatus> status = new AtomicReference<>();
|
||||
licensesService.registerLicense(putLicenseRequest, new ActionListener<LicensesService.LicensesUpdateResponse>() {
|
||||
licensesService.registerLicense(putLicenseRequest, new ActionListener<PutLicenseResponse>() {
|
||||
@Override
|
||||
public void onResponse(LicensesService.LicensesUpdateResponse licensesUpdateResponse) {
|
||||
public void onResponse(PutLicenseResponse licensesUpdateResponse) {
|
||||
status.set(licensesUpdateResponse.status());
|
||||
latch.countDown();
|
||||
}
|
||||
@ -198,4 +218,4 @@ public class TestUtils {
|
||||
statuses.add(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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.settings.Settings;
|
||||
|
||||
/**
|
||||
* Registers licenses upon the start of the service lifecycle
|
||||
* see {@link EagerLicenseRegistrationPluginService}
|
||||
* <p>
|
||||
* License registration might happen before clusterService start()
|
||||
*/
|
||||
public class EagerLicenseRegistrationConsumerPlugin extends TestConsumerPluginBase {
|
||||
|
||||
public static final String NAME = "test_consumer_plugin_1";
|
||||
|
||||
@Inject
|
||||
public EagerLicenseRegistrationConsumerPlugin(Settings settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends TestPluginServiceBase> service() {
|
||||
return EagerLicenseRegistrationPluginService.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return EagerLicenseRegistrationPluginService.ID;
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* 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.LicensesService;
|
||||
|
||||
@Singleton
|
||||
public class EagerLicenseRegistrationPluginService extends TestPluginServiceBase {
|
||||
|
||||
public static String ID = "id1";
|
||||
|
||||
@Inject
|
||||
public EagerLicenseRegistrationPluginService(Settings settings, LicensesService licensesClientService) {
|
||||
super(true, settings, licensesClientService, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return ID;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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.settings.Settings;
|
||||
|
||||
/**
|
||||
* Registers licenses only after cluster has recovered
|
||||
* see {@link LazyLicenseRegistrationPluginService}
|
||||
* <p>
|
||||
* License registration happens after clusterservice start()
|
||||
*/
|
||||
public class LazyLicenseRegistrationConsumerPlugin extends TestConsumerPluginBase {
|
||||
|
||||
@Inject
|
||||
public LazyLicenseRegistrationConsumerPlugin(Settings settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends TestPluginServiceBase> service() {
|
||||
return LazyLicenseRegistrationPluginService.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return LazyLicenseRegistrationPluginService.ID;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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.cluster.service.ClusterService;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.inject.Singleton;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
||||
|
||||
@Singleton
|
||||
public class LazyLicenseRegistrationPluginService extends TestPluginServiceBase {
|
||||
|
||||
public static String ID = "id2";
|
||||
|
||||
@Inject
|
||||
public LazyLicenseRegistrationPluginService(Settings settings, LicensesService licensesClientService, ClusterService clusterService) {
|
||||
super(false, settings, licensesClientService, clusterService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return ID;
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.client.Client;
|
||||
import org.elasticsearch.client.transport.TransportClient;
|
||||
import org.elasticsearch.common.component.LifecycleComponent;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsModule;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class TestConsumerPluginBase extends Plugin {
|
||||
|
||||
private final boolean isEnabled;
|
||||
|
||||
public TestConsumerPluginBase(Settings settings) {
|
||||
this.isEnabled = TransportClient.CLIENT_TYPE.equals(settings.get(Client.CLIENT_TYPE_SETTING_S.getKey())) == false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends LifecycleComponent>> nodeServices() {
|
||||
Collection<Class<? extends LifecycleComponent>> services = new ArrayList<>();
|
||||
if (isEnabled) {
|
||||
services.add(service());
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Setting<?>> getSettings() {
|
||||
return Arrays.asList(Setting.simpleString("_trial_license_duration_in_seconds", Setting.Property.NodeScope,
|
||||
Setting.Property.Shared), Setting.simpleString("_grace_duration_in_seconds", Setting.Property.NodeScope,
|
||||
Setting.Property.Shared));
|
||||
}
|
||||
|
||||
public abstract Class<? extends TestPluginServiceBase> service();
|
||||
|
||||
public abstract String id();
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
* 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.ElasticsearchException;
|
||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||
import org.elasticsearch.cluster.service.ClusterService;
|
||||
import org.elasticsearch.cluster.ClusterStateListener;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.gateway.GatewayService;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.core.LicenseState;
|
||||
import org.elasticsearch.license.plugin.core.Licensee;
|
||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public abstract class TestPluginServiceBase extends AbstractLifecycleComponent
|
||||
implements ClusterStateListener, Licensee {
|
||||
|
||||
private LicensesService licensesClientService;
|
||||
private final ClusterService clusterService;
|
||||
final boolean eagerLicenseRegistration;
|
||||
public final AtomicBoolean registered = new AtomicBoolean(false);
|
||||
private AtomicReference<LicenseState> state = new AtomicReference<>(LicenseState.DISABLED);
|
||||
|
||||
public TestPluginServiceBase(boolean eagerLicenseRegistration, Settings settings, LicensesService licensesClientService,
|
||||
ClusterService clusterService) {
|
||||
super(settings);
|
||||
this.eagerLicenseRegistration = eagerLicenseRegistration;
|
||||
this.licensesClientService = licensesClientService;
|
||||
int trialDurationInSec = settings.getAsInt("_trial_license_duration_in_seconds", -1);
|
||||
if (trialDurationInSec != -1) {
|
||||
licensesClientService.setTrialLicenseDuration(TimeValue.timeValueSeconds(trialDurationInSec));
|
||||
}
|
||||
int graceDurationInSec = settings.getAsInt("_grace_duration_in_seconds", 5);
|
||||
licensesClientService.setGracePeriodDuration(TimeValue.timeValueSeconds(graceDurationInSec));
|
||||
if (!eagerLicenseRegistration) {
|
||||
this.clusterService = clusterService;
|
||||
clusterService.add(this);
|
||||
} else {
|
||||
this.clusterService = null;
|
||||
}
|
||||
}
|
||||
|
||||
// should be the same string used by the license Manger to generate
|
||||
// signed license
|
||||
public abstract String id();
|
||||
|
||||
// check if feature is enabled
|
||||
public LicenseState state() {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clusterChanged(ClusterChangedEvent event) {
|
||||
if (!eagerLicenseRegistration && !event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
||||
if (registered.compareAndSet(false, true)) {
|
||||
logger.info("Registering to licensesService [lazy]");
|
||||
licensesClientService.register(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void doStart() throws ElasticsearchException {
|
||||
if (eagerLicenseRegistration) {
|
||||
if (registered.compareAndSet(false, true)) {
|
||||
logger.info("Registering to licensesService [eager]");
|
||||
licensesClientService.register(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] expirationMessages() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(Status status) {
|
||||
this.state.set(status.getLicenseState());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws ElasticsearchException {
|
||||
if (clusterService != null) {
|
||||
clusterService.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() throws ElasticsearchException {
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.block.ClusterBlocks;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
||||
import org.elasticsearch.cluster.service.ClusterService;
|
||||
import org.elasticsearch.common.component.Lifecycle;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.transport.LocalTransportAddress;
|
||||
import org.elasticsearch.license.core.License;
|
||||
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.junit.Before;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.emptySet;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public abstract class AbstractLicenseServiceTestCase extends ESTestCase {
|
||||
|
||||
protected LicensesService licensesService;
|
||||
protected ClusterService clusterService;
|
||||
protected TransportService transportService;
|
||||
protected ClockMock clock;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
clusterService = mock(ClusterService.class);
|
||||
transportService = mock(TransportService.class);
|
||||
clock = new ClockMock();
|
||||
licensesService = new LicensesService(Settings.EMPTY, clusterService, transportService, clock);
|
||||
}
|
||||
|
||||
protected void setInitialState(License license) {
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
final ClusterBlocks noBlock = ClusterBlocks.builder().build();
|
||||
when(state.blocks()).thenReturn(noBlock);
|
||||
MetaData metaData = mock(MetaData.class);
|
||||
when(metaData.custom(LicensesMetaData.TYPE)).thenReturn(new LicensesMetaData(license));
|
||||
when(state.metaData()).thenReturn(metaData);
|
||||
final DiscoveryNodes discoveryNodes = mock(DiscoveryNodes.class);
|
||||
final DiscoveryNode mockNode = new DiscoveryNode("b", LocalTransportAddress.buildUnique(), emptyMap(), emptySet(), Version.CURRENT);
|
||||
when(discoveryNodes.getMasterNode()).thenReturn(mockNode);
|
||||
when(state.nodes()).thenReturn(discoveryNodes);
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
when(clusterService.lifecycleState()).thenReturn(Lifecycle.State.STARTED);
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import static org.elasticsearch.common.unit.TimeValue.timeValueMillis;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
public class ExpirationCallbackTests extends ESTestCase {
|
||||
|
||||
public void testPostExpirationDelay() throws Exception {
|
||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(randomIntBetween(5, 10));
|
||||
TimeValue min = TimeValue.timeValueSeconds(1);
|
||||
TimeValue max = TimeValue.timeValueSeconds(4);
|
||||
TimeValue frequency = TimeValue.timeValueSeconds(1);
|
||||
NoopPostExpirationCallback post = new NoopPostExpirationCallback(min, max, frequency);
|
||||
long now = System.currentTimeMillis();
|
||||
long expiryDate = now + expiryDuration.getMillis();
|
||||
assertThat(post.delay(expiryDate, now),
|
||||
equalTo(TimeValue.timeValueMillis(expiryDuration.getMillis() + min.getMillis()))); // before license expiry
|
||||
assertThat(post.delay(expiryDate, expiryDate), equalTo(min)); // on license expiry
|
||||
int latestValidTriggerDelay = (int) (expiryDuration.getMillis() + max.getMillis());
|
||||
int earliestValidTriggerDelay = (int) (expiryDuration.getMillis() + min.getMillis());
|
||||
assertExpirationCallbackDelay(post, expiryDuration.millis(), latestValidTriggerDelay, earliestValidTriggerDelay);
|
||||
}
|
||||
|
||||
public void testPreExpirationDelay() throws Exception {
|
||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(randomIntBetween(5, 10));
|
||||
TimeValue min = TimeValue.timeValueSeconds(1);
|
||||
TimeValue max = TimeValue.timeValueSeconds(4);
|
||||
TimeValue frequency = TimeValue.timeValueSeconds(1);
|
||||
NoopPreExpirationCallback pre = new NoopPreExpirationCallback(min, max, frequency);
|
||||
long now = System.currentTimeMillis();
|
||||
long expiryDate = now + expiryDuration.getMillis();
|
||||
assertThat(pre.delay(expiryDate, expiryDate), nullValue()); // on license expiry
|
||||
int latestValidTriggerDelay = (int) (expiryDuration.getMillis() - min.getMillis());
|
||||
int earliestValidTriggerDelay = (int) (expiryDuration.getMillis() - max.getMillis());
|
||||
assertExpirationCallbackDelay(pre, expiryDuration.millis(), latestValidTriggerDelay, earliestValidTriggerDelay);
|
||||
}
|
||||
|
||||
public void testPostExpirationWithNullMax() throws Exception {
|
||||
int postExpirySeconds = randomIntBetween(5, 10);
|
||||
TimeValue postExpiryDuration = TimeValue.timeValueSeconds(postExpirySeconds);
|
||||
TimeValue min = TimeValue.timeValueSeconds(postExpirySeconds - randomIntBetween(1, 3));
|
||||
|
||||
final ExpirationCallback.Post post = new NoopPostExpirationCallback(min, null, timeValueMillis(10));
|
||||
long now = System.currentTimeMillis();
|
||||
assertThat(post.delay(now - postExpiryDuration.millis(), now), equalTo(TimeValue.timeValueMillis(0)));
|
||||
}
|
||||
|
||||
public void testPreExpirationWithNullMin() throws Exception {
|
||||
int expirySeconds = randomIntBetween(5, 10);
|
||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(expirySeconds);
|
||||
TimeValue max = TimeValue.timeValueSeconds(expirySeconds + randomIntBetween(1, 10));
|
||||
|
||||
final ExpirationCallback.Pre pre = new NoopPreExpirationCallback(null, max, timeValueMillis(10));
|
||||
long now = System.currentTimeMillis();
|
||||
assertThat(pre.delay(expiryDuration.millis() + now, now), equalTo(TimeValue.timeValueMillis(0)));
|
||||
}
|
||||
|
||||
public void testPreExpirationScheduleTime() throws Exception {
|
||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(randomIntBetween(5, 10));
|
||||
TimeValue min = TimeValue.timeValueSeconds(1);
|
||||
TimeValue max = TimeValue.timeValueSeconds(4);
|
||||
TimeValue frequency = TimeValue.timeValueSeconds(1);
|
||||
NoopPreExpirationCallback pre = new NoopPreExpirationCallback(min, max, frequency);
|
||||
int latestValidTriggerDelay = (int) (expiryDuration.getMillis() - min.getMillis());
|
||||
int earliestValidTriggerDelay = (int) (expiryDuration.getMillis() - max.getMillis());
|
||||
assertExpirationCallbackScheduleTime(pre, expiryDuration.millis(), latestValidTriggerDelay, earliestValidTriggerDelay);
|
||||
}
|
||||
|
||||
public void testPostExpirationScheduleTime() throws Exception {
|
||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(randomIntBetween(5, 10));
|
||||
TimeValue min = TimeValue.timeValueSeconds(1);
|
||||
TimeValue max = TimeValue.timeValueSeconds(4);
|
||||
TimeValue frequency = TimeValue.timeValueSeconds(1);
|
||||
NoopPostExpirationCallback pre = new NoopPostExpirationCallback(min, max, frequency);
|
||||
int latestValidTriggerDelay = (int) (expiryDuration.getMillis() + max.getMillis());
|
||||
int earliestValidTriggerDelay = (int) (expiryDuration.getMillis() + min.getMillis());
|
||||
assertExpirationCallbackScheduleTime(pre, expiryDuration.millis(), latestValidTriggerDelay, earliestValidTriggerDelay);
|
||||
}
|
||||
|
||||
private void assertExpirationCallbackDelay(ExpirationCallback expirationCallback, long expiryDuration,
|
||||
int latestValidTriggerDelay, int earliestValidTriggerDelay) {
|
||||
long now = System.currentTimeMillis();
|
||||
long expiryDate = now + expiryDuration;
|
||||
// bounds
|
||||
assertThat(expirationCallback.delay(expiryDate, now + earliestValidTriggerDelay), equalTo(TimeValue.timeValueMillis(0)));
|
||||
assertThat(expirationCallback.delay(expiryDate, now + latestValidTriggerDelay), equalTo(TimeValue.timeValueMillis(0)));
|
||||
// in match
|
||||
assertThat(expirationCallback.delay(expiryDate,
|
||||
now + randomIntBetween(earliestValidTriggerDelay, latestValidTriggerDelay)),
|
||||
equalTo(TimeValue.timeValueMillis(0)));
|
||||
// out of bounds
|
||||
int deltaBeforeEarliestMatch = between(1, earliestValidTriggerDelay);
|
||||
assertThat(expirationCallback.delay(expiryDate, now + deltaBeforeEarliestMatch),
|
||||
equalTo(TimeValue.timeValueMillis(earliestValidTriggerDelay - deltaBeforeEarliestMatch)));
|
||||
int deltaAfterLatestMatch = between(latestValidTriggerDelay + 1, Integer.MAX_VALUE); // after expiry and after max
|
||||
assertThat(expirationCallback.delay(expiryDate, expiryDate + deltaAfterLatestMatch), nullValue());
|
||||
}
|
||||
|
||||
public void assertExpirationCallbackScheduleTime(ExpirationCallback expirationCallback, long expiryDuration,
|
||||
int latestValidTriggerDelay, int earliestValidTriggerDelay) {
|
||||
long now = System.currentTimeMillis();
|
||||
long expiryDate = now + expiryDuration;
|
||||
int validTriggerInterval = between(earliestValidTriggerDelay, latestValidTriggerDelay);
|
||||
assertThat(expirationCallback.nextScheduledTimeForExpiry(expiryDate,
|
||||
now + validTriggerInterval, now + validTriggerInterval),
|
||||
equalTo(now + validTriggerInterval));
|
||||
assertThat(expirationCallback.nextScheduledTimeForExpiry(expiryDate, now, now + validTriggerInterval),
|
||||
equalTo(now + validTriggerInterval + expirationCallback.getFrequency()));
|
||||
|
||||
int deltaBeforeEarliestMatch = between(1, earliestValidTriggerDelay);
|
||||
assertThat(expirationCallback.nextScheduledTimeForExpiry(expiryDate, now, now + deltaBeforeEarliestMatch),
|
||||
equalTo(now + deltaBeforeEarliestMatch +
|
||||
expirationCallback.delay(expiryDate, now + deltaBeforeEarliestMatch).getMillis()));
|
||||
assertThat(expirationCallback.nextScheduledTimeForExpiry(expiryDate,
|
||||
now + deltaBeforeEarliestMatch, now + deltaBeforeEarliestMatch),
|
||||
equalTo(now + deltaBeforeEarliestMatch +
|
||||
expirationCallback.delay(expiryDate, now + deltaBeforeEarliestMatch).getMillis()));
|
||||
|
||||
int deltaAfterLatestMatch = between(latestValidTriggerDelay + 1, Integer.MAX_VALUE); // after expiry and after max
|
||||
assertThat(expirationCallback.nextScheduledTimeForExpiry(expiryDate, now, now + deltaAfterLatestMatch), equalTo(-1L));
|
||||
assertThat(expirationCallback.nextScheduledTimeForExpiry(expiryDate,
|
||||
now + deltaAfterLatestMatch, now + deltaAfterLatestMatch),
|
||||
equalTo(-1L));
|
||||
}
|
||||
|
||||
private static class NoopPostExpirationCallback extends ExpirationCallback.Post {
|
||||
|
||||
public NoopPostExpirationCallback(TimeValue min, TimeValue max, TimeValue frequency) {
|
||||
super(min, max, frequency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void on(License license) {}
|
||||
}
|
||||
|
||||
private static class NoopPreExpirationCallback extends ExpirationCallback.Pre {
|
||||
|
||||
public NoopPreExpirationCallback(TimeValue min, TimeValue max, TimeValue frequency) {
|
||||
super(min, max, frequency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void on(License license) {}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||
import org.elasticsearch.cluster.ClusterName;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
||||
import org.elasticsearch.common.transport.LocalTransportAddress;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.TestUtils;
|
||||
import org.elasticsearch.transport.EmptyTransportResponseHandler;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.emptySet;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase {
|
||||
|
||||
private TestUtils.AssertingLicensee licensee;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
setInitialState(null);
|
||||
licensesService.start();
|
||||
licensee = new TestUtils.AssertingLicensee("LicenseClusterChangeTests", logger);
|
||||
licensesService.register(licensee);
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
|
||||
public void testNotificationOnNewLicense() throws Exception {
|
||||
ClusterState oldState = ClusterState.builder(new ClusterName("a")).build();
|
||||
final License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
|
||||
MetaData metaData = MetaData.builder().putCustom(LicensesMetaData.TYPE, new LicensesMetaData(license)).build();
|
||||
ClusterState newState = ClusterState.builder(new ClusterName("a")).metaData(metaData).build();
|
||||
licensesService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState));
|
||||
assertThat(licensee.statuses.size(), equalTo(1));
|
||||
assertTrue(licensee.statuses.get(0).getLicenseState() == LicenseState.ENABLED);
|
||||
}
|
||||
|
||||
public void testNoNotificationOnExistingLicense() throws Exception {
|
||||
final License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
|
||||
MetaData metaData = MetaData.builder().putCustom(LicensesMetaData.TYPE, new LicensesMetaData(license)).build();
|
||||
ClusterState newState = ClusterState.builder(new ClusterName("a")).metaData(metaData).build();
|
||||
ClusterState oldState = ClusterState.builder(newState).build();
|
||||
licensesService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState));
|
||||
assertThat(licensee.statuses.size(), equalTo(0));
|
||||
}
|
||||
|
||||
public void testTrialLicenseGeneration() throws Exception {
|
||||
DiscoveryNode master = new DiscoveryNode("b", LocalTransportAddress.buildUnique(), emptyMap(), emptySet(), Version.CURRENT);
|
||||
ClusterState oldState = ClusterState.builder(new ClusterName("a"))
|
||||
.nodes(DiscoveryNodes.builder().masterNodeId(master.getId()).put(master)).build();
|
||||
ClusterState newState = ClusterState.builder(oldState).build();
|
||||
licensesService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState));
|
||||
verify(transportService, times(2))
|
||||
.sendRequest(any(DiscoveryNode.class),
|
||||
eq(LicensesService.REGISTER_TRIAL_LICENSE_ACTION_NAME),
|
||||
any(TransportRequest.Empty.class), any(EmptyTransportResponseHandler.class));
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.plugin.TestUtils;
|
||||
import org.elasticsearch.transport.EmptyTransportResponseHandler;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
import static org.elasticsearch.mock.orig.Mockito.times;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase {
|
||||
|
||||
public void testTrialLicenseRequestOnEmptyLicenseState() throws Exception {
|
||||
setInitialState(null);
|
||||
TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee(
|
||||
"testTrialLicenseRequestOnEmptyLicenseState", logger);
|
||||
licensesService.start();
|
||||
licensesService.register(licensee);
|
||||
verify(transportService, times(1))
|
||||
.sendRequest(any(DiscoveryNode.class),
|
||||
eq(LicensesService.REGISTER_TRIAL_LICENSE_ACTION_NAME),
|
||||
any(TransportRequest.Empty.class), any(EmptyTransportResponseHandler.class));
|
||||
assertThat(licensee.statuses.size(), equalTo(0));
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testNotificationOnRegistration() throws Exception {
|
||||
setInitialState(TestUtils.generateSignedLicense(TimeValue.timeValueHours(2)));
|
||||
TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee(
|
||||
"testNotificationOnRegistration", logger);
|
||||
licensesService.start();
|
||||
licensesService.register(licensee);
|
||||
assertThat(licensee.statuses.size(), equalTo(1));
|
||||
final LicenseState licenseState = licensee.statuses.get(0).getLicenseState();
|
||||
assertTrue(licenseState == LicenseState.ENABLED);
|
||||
licensesService.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.TestUtils;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class LicenseScheduleTests extends ESTestCase {
|
||||
|
||||
private License license;
|
||||
private LicenseSchedule schedule;
|
||||
|
||||
@Before
|
||||
public void setuo() throws Exception {
|
||||
license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(12));
|
||||
schedule = new LicenseSchedule(license);
|
||||
}
|
||||
|
||||
public void testEnabledLicenseSchedule() throws Exception {
|
||||
int expiryDuration = (int) (license.expiryDate() - license.issueDate());
|
||||
long triggeredTime = license.issueDate() + between(0, expiryDuration);
|
||||
assertThat(schedule.nextScheduledTimeAfter(license.issueDate(), triggeredTime), equalTo(license.expiryDate()));
|
||||
}
|
||||
|
||||
public void testGraceLicenseSchedule() throws Exception {
|
||||
long triggeredTime = license.expiryDate() + between(1,
|
||||
((int) LicensesService.GRACE_PERIOD_DURATION.getMillis()));
|
||||
assertThat(schedule.nextScheduledTimeAfter(license.issueDate(), triggeredTime),
|
||||
equalTo(license.expiryDate() + LicensesService.GRACE_PERIOD_DURATION.getMillis()));
|
||||
}
|
||||
|
||||
public void testExpiredLicenseSchedule() throws Exception {
|
||||
long triggeredTime = license.expiryDate() + LicensesService.GRACE_PERIOD_DURATION.getMillis() +
|
||||
randomIntBetween(1, 1000);
|
||||
assertThat(schedule.nextScheduledTimeAfter(license.issueDate(), triggeredTime),
|
||||
equalTo(-1L));
|
||||
}
|
||||
|
||||
public void testInvalidLicenseSchedule() throws Exception {
|
||||
long triggeredTime = license.issueDate() - randomIntBetween(1, 1000);
|
||||
assertThat(schedule.nextScheduledTimeAfter(triggeredTime, triggeredTime),
|
||||
equalTo(license.issueDate()));
|
||||
}
|
||||
}
|
@ -6,79 +6,58 @@
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.cluster.ClusterStateUpdateTask;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.TestUtils;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
|
||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.elasticsearch.license.plugin.TestUtils.awaitNoBlock;
|
||||
import static org.elasticsearch.license.plugin.TestUtils.awaitNoPendingTasks;
|
||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class LicensesAcknowledgementTests extends ESSingleNodeTestCase {
|
||||
static {
|
||||
MetaData.registerPrototype(LicensesMetaData.TYPE, LicensesMetaData.PROTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean resetNodeAfterTest() {
|
||||
return true;
|
||||
}
|
||||
public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase {
|
||||
|
||||
public void testAcknowledgment() throws Exception {
|
||||
final LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2)));
|
||||
licensesService.start();
|
||||
String id = "testAcknowledgment";
|
||||
String[] acknowledgeMessages = new String[] {"message"};
|
||||
TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee(id, logger);
|
||||
licensee.setAcknowledgementMessages(acknowledgeMessages);
|
||||
awaitNoBlock(client());
|
||||
licensesService.register(licensee);
|
||||
awaitNoPendingTasks(client());
|
||||
// try installing a signed license
|
||||
License signedLicense = generateSignedLicense(TimeValue.timeValueHours(10));
|
||||
PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(signedLicense);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
// ensure acknowledgement message was part of the response
|
||||
licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID,
|
||||
Collections.singletonMap(id, acknowledgeMessages), latch));
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for a response to license registration");
|
||||
}
|
||||
awaitNoPendingTasks(client());
|
||||
Collections.singletonMap(id, acknowledgeMessages)));
|
||||
assertThat(licensee.acknowledgementRequested.size(), equalTo(1));
|
||||
assertThat(licensee.acknowledgementRequested.get(0).v2(), equalTo(signedLicense));
|
||||
assertThat(licensesService.getLicense(), not(signedLicense));
|
||||
|
||||
latch = new CountDownLatch(1);
|
||||
// try installing a signed license with acknowledgement
|
||||
putLicenseRequest = new PutLicenseRequest().license(signedLicense).acknowledge(true);
|
||||
// ensure license was installed and no acknowledgment message was returned
|
||||
licensee.setAcknowledgementMessages(new String[0]);
|
||||
licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(true, LicensesStatus.VALID,
|
||||
Collections.<String, String[]>emptyMap(), latch));
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for a response to license registration");
|
||||
}
|
||||
awaitNoPendingTasks(client());
|
||||
Collections.<String, String[]>emptyMap()));
|
||||
verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
|
||||
assertThat(licensee.acknowledgementRequested.size(), equalTo(1));
|
||||
assertThat(licensee.acknowledgementRequested.get(0).v2(), equalTo(signedLicense));
|
||||
assertThat(licensesService.getLicense(), equalTo(signedLicense));
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testAcknowledgementMultipleLicensee() throws Exception {
|
||||
final LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2)));
|
||||
licensesService.start();
|
||||
String id1 = "testAcknowledgementMultipleLicensee_1";
|
||||
String[] acknowledgeMessages1 = new String[] {"testAcknowledgementMultipleLicensee_1"};
|
||||
@ -88,62 +67,49 @@ public class LicensesAcknowledgementTests extends ESSingleNodeTestCase {
|
||||
licensee1.setAcknowledgementMessages(acknowledgeMessages1);
|
||||
TestUtils.AssertingLicensee licensee2 = new TestUtils.AssertingLicensee(id2, logger);
|
||||
licensee2.setAcknowledgementMessages(acknowledgeMessages2);
|
||||
awaitNoBlock(client());
|
||||
licensesService.register(licensee1);
|
||||
licensesService.register(licensee2);
|
||||
awaitNoPendingTasks(client());
|
||||
// try installing a signed license
|
||||
License signedLicense = generateSignedLicense(TimeValue.timeValueHours(10));
|
||||
PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(signedLicense);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
// ensure acknowledgement message was part of the response
|
||||
final HashMap<String, String[]> expectedMessages = new HashMap<>();
|
||||
expectedMessages.put(id1, acknowledgeMessages1);
|
||||
expectedMessages.put(id2, acknowledgeMessages2);
|
||||
licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID,
|
||||
expectedMessages, latch));
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for a response to license registration");
|
||||
}
|
||||
awaitNoPendingTasks(client());
|
||||
expectedMessages));
|
||||
verify(clusterService, times(0)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
|
||||
assertThat(licensee2.acknowledgementRequested.size(), equalTo(1));
|
||||
assertThat(licensee2.acknowledgementRequested.get(0).v2(), equalTo(signedLicense));
|
||||
assertThat(licensee1.acknowledgementRequested.size(), equalTo(1));
|
||||
assertThat(licensee1.acknowledgementRequested.get(0).v2(), equalTo(signedLicense));
|
||||
assertThat(licensesService.getLicense(), not(signedLicense));
|
||||
|
||||
latch = new CountDownLatch(1);
|
||||
// try installing a signed license with acknowledgement
|
||||
putLicenseRequest = new PutLicenseRequest().license(signedLicense).acknowledge(true);
|
||||
// ensure license was installed and no acknowledgment message was returned
|
||||
licensee1.setAcknowledgementMessages(new String[0]);
|
||||
licensee2.setAcknowledgementMessages(new String[0]);
|
||||
licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(true, LicensesStatus.VALID,
|
||||
Collections.<String, String[]>emptyMap(), latch));
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for a response to license registration");
|
||||
}
|
||||
awaitNoPendingTasks(client());
|
||||
assertThat(licensesService.getLicense(), equalTo(signedLicense));
|
||||
Collections.<String, String[]>emptyMap()));
|
||||
verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
private static class AssertingLicensesUpdateResponse implements ActionListener<LicensesService.LicensesUpdateResponse> {
|
||||
private static class AssertingLicensesUpdateResponse implements ActionListener<PutLicenseResponse> {
|
||||
private final boolean expectedAcknowledgement;
|
||||
private final LicensesStatus expectedStatus;
|
||||
private final Map<String, String[]> expectedAckMessages;
|
||||
private final CountDownLatch latch;
|
||||
|
||||
public AssertingLicensesUpdateResponse(boolean expectedAcknowledgement, LicensesStatus expectedStatus,
|
||||
Map<String, String[]> expectedAckMessages, CountDownLatch latch) {
|
||||
Map<String, String[]> expectedAckMessages) {
|
||||
this.expectedAcknowledgement = expectedAcknowledgement;
|
||||
this.expectedStatus = expectedStatus;
|
||||
this.expectedAckMessages = expectedAckMessages;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(LicensesService.LicensesUpdateResponse licensesUpdateResponse) {
|
||||
public void onResponse(PutLicenseResponse licensesUpdateResponse) {
|
||||
assertThat(licensesUpdateResponse.isAcknowledged(), equalTo(expectedAcknowledgement));
|
||||
assertThat(licensesUpdateResponse.status(), equalTo(expectedStatus));
|
||||
assertThat(licensesUpdateResponse.acknowledgeMessages().size(), equalTo(expectedAckMessages.size()));
|
||||
@ -153,12 +119,10 @@ public class LicensesAcknowledgementTests extends ESSingleNodeTestCase {
|
||||
String[] actualMessages = actual.get(expectedEntry.getKey());
|
||||
assertThat(actualMessages, equalTo(expectedEntry.getValue()));
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception throwable) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.TestUtils;
|
||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.elasticsearch.common.unit.TimeValue.timeValueMillis;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class LicensesExpirationCallbackTests extends ESSingleNodeTestCase {
|
||||
static {
|
||||
MetaData.registerPrototype(LicensesMetaData.TYPE, LicensesMetaData.PROTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean resetNodeAfterTest() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void testPostExpiration() throws Exception {
|
||||
int postExpirySeconds = randomIntBetween(5, 10);
|
||||
TimeValue postExpiryDuration = TimeValue.timeValueSeconds(postExpirySeconds);
|
||||
TimeValue min = TimeValue.timeValueSeconds(postExpirySeconds - randomIntBetween(1, 3));
|
||||
TimeValue max = TimeValue.timeValueSeconds(postExpirySeconds + randomIntBetween(1, 10));
|
||||
|
||||
final LicensesService.ExpirationCallback.Post post = new LicensesService.ExpirationCallback.Post(min, max, timeValueMillis(10)) {
|
||||
@Override
|
||||
public void on(License license) {
|
||||
}
|
||||
};
|
||||
long now = System.currentTimeMillis();
|
||||
assertThat(post.matches(now - postExpiryDuration.millis(), now), equalTo(true));
|
||||
assertThat(post.matches(now + postExpiryDuration.getMillis(), now), equalTo(false));
|
||||
}
|
||||
|
||||
public void testPostExpirationWithNullMax() throws Exception {
|
||||
int postExpirySeconds = randomIntBetween(5, 10);
|
||||
TimeValue postExpiryDuration = TimeValue.timeValueSeconds(postExpirySeconds);
|
||||
TimeValue min = TimeValue.timeValueSeconds(postExpirySeconds - randomIntBetween(1, 3));
|
||||
|
||||
final LicensesService.ExpirationCallback.Post post = new LicensesService.ExpirationCallback.Post(min, null, timeValueMillis(10)) {
|
||||
@Override
|
||||
public void on(License license) {
|
||||
}
|
||||
};
|
||||
long now = System.currentTimeMillis();
|
||||
assertThat(post.matches(now - postExpiryDuration.millis(), now), equalTo(true));
|
||||
}
|
||||
|
||||
public void testPreExpirationWithNullMin() throws Exception {
|
||||
int expirySeconds = randomIntBetween(5, 10);
|
||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(expirySeconds);
|
||||
TimeValue max = TimeValue.timeValueSeconds(expirySeconds + randomIntBetween(1, 10));
|
||||
|
||||
final LicensesService.ExpirationCallback.Pre pre = new LicensesService.ExpirationCallback.Pre(null, max, timeValueMillis(10)) {
|
||||
@Override
|
||||
public void on(License license) {
|
||||
}
|
||||
};
|
||||
long now = System.currentTimeMillis();
|
||||
assertThat(pre.matches(expiryDuration.millis() + now, now), equalTo(true));
|
||||
}
|
||||
|
||||
public void testPreExpiration() throws Exception {
|
||||
int expirySeconds = randomIntBetween(5, 10);
|
||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(expirySeconds);
|
||||
TimeValue min = TimeValue.timeValueSeconds(expirySeconds - randomIntBetween(0, 3));
|
||||
TimeValue max = TimeValue.timeValueSeconds(expirySeconds + randomIntBetween(1, 10));
|
||||
|
||||
final LicensesService.ExpirationCallback.Pre pre = new LicensesService.ExpirationCallback.Pre(min, max, timeValueMillis(10)) {
|
||||
@Override
|
||||
public void on(License license) {
|
||||
}
|
||||
};
|
||||
long now = System.currentTimeMillis();
|
||||
assertThat(pre.matches(expiryDuration.millis() + now, now), equalTo(true));
|
||||
assertThat(pre.matches(now - expiryDuration.getMillis(), now), equalTo(false));
|
||||
}
|
||||
|
||||
public void testPreExpirationNotification() throws Exception {
|
||||
final LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(5));
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
// 2000, 1600, 1200
|
||||
licensesService.setExpirationCallbacks(Collections.singletonList(
|
||||
preCallbackLatch(TimeValue.timeValueSeconds(1), TimeValue.timeValueSeconds(2), timeValueMillis(400), counter))
|
||||
);
|
||||
licensesService.start();
|
||||
TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee("testPreExpirationNotification", logger);
|
||||
licensesService.register(licensee);
|
||||
boolean success = awaitBusy(() -> (counter.get() == 3 || counter.get() == 2));
|
||||
assertThat("counter: actual: " + counter.get() + "vs expected: 3", success, equalTo(true));
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testPostExpirationNotification() throws Exception {
|
||||
final LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(3));
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
// 700, 1700, 2700
|
||||
licensesService.setExpirationCallbacks(Collections.singletonList(
|
||||
postCallbackLatch(timeValueMillis(700), TimeValue.timeValueSeconds(3), TimeValue.timeValueSeconds(1), counter))
|
||||
);
|
||||
licensesService.start();
|
||||
TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee("testPostExpirationNotification", logger);
|
||||
licensesService.register(licensee);
|
||||
// callback can be called only twice if the third notification is triggered with a delay
|
||||
// causing the trigger time to be out of the post expiry callback window
|
||||
boolean success = awaitBusy(() -> (counter.get() == 3 || counter.get() == 2));
|
||||
assertThat("counter: actual: " + counter.get() + "vs expected: 3", success, equalTo(true));
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testMultipleExpirationNotification() throws Exception {
|
||||
final LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(4));
|
||||
AtomicInteger postCounter = new AtomicInteger(0);
|
||||
AtomicInteger preCounter = new AtomicInteger(0);
|
||||
licensesService.setExpirationCallbacks(Arrays.asList(
|
||||
// 2000, 1600, 1200
|
||||
preCallbackLatch(TimeValue.timeValueSeconds(1), TimeValue.timeValueSeconds(2), timeValueMillis(400), preCounter),
|
||||
// 100, 500, 900, 1300, 1700
|
||||
postCallbackLatch(timeValueMillis(100), TimeValue.timeValueSeconds(2), timeValueMillis(400), postCounter))
|
||||
);
|
||||
licensesService.start();
|
||||
TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee("testMultipleExpirationNotification", logger);
|
||||
licensesService.register(licensee);
|
||||
// callback can be called one less than expected if the last notification is triggered
|
||||
// with a delay, causing the trigger time to be out of the expiry callback window
|
||||
boolean success = awaitBusy(() -> ((preCounter.get() == 3 || preCounter.get() == 2)
|
||||
&& (postCounter.get() == 5 || postCounter.get() == 4)));
|
||||
assertThat("post count: actual: " + postCounter.get() + "vs expected: 5 " +
|
||||
"pre count: actual: " + preCounter.get() + " vs expected: 3", success, equalTo(true));
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
private static LicensesService.ExpirationCallback preCallbackLatch(TimeValue min, TimeValue max, TimeValue frequency,
|
||||
final AtomicInteger count) {
|
||||
return new LicensesService.ExpirationCallback.Pre(min, max, frequency) {
|
||||
@Override
|
||||
public void on(License license) {
|
||||
count.incrementAndGet();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static LicensesService.ExpirationCallback postCallbackLatch(TimeValue min, TimeValue max, TimeValue frequency,
|
||||
final AtomicInteger count) {
|
||||
return new LicensesService.ExpirationCallback.Post(min, max, frequency) {
|
||||
@Override
|
||||
public void on(License license) {
|
||||
count.incrementAndGet();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase.BadApple;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.plugin.TestUtils.AssertingLicensee;
|
||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.license.plugin.TestUtils.awaitNoBlock;
|
||||
import static org.elasticsearch.license.plugin.TestUtils.awaitNoPendingTasks;
|
||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
||||
import static org.elasticsearch.license.plugin.TestUtils.registerAndAckSignedLicenses;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
//test is just too slow, please fix it to not be sleep-based
|
||||
@BadApple(bugUrl = "https://github.com/elastic/x-plugins/issues/1007")
|
||||
public class LicensesExpiryNotificationTests extends ESSingleNodeTestCase {
|
||||
static {
|
||||
MetaData.registerPrototype(LicensesMetaData.TYPE, LicensesMetaData.PROTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean resetNodeAfterTest() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void testTrialLicenseEnforcement() throws Exception {
|
||||
LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(5));
|
||||
licensesService.setGracePeriodDuration(TimeValue.timeValueSeconds(3));
|
||||
licensesService.start();
|
||||
String id1 = "testTrialLicenseEnforcement";
|
||||
final AssertingLicensee licensee = new AssertingLicensee(id1, logger);
|
||||
awaitNoBlock(client());
|
||||
licensesService.register(licensee);
|
||||
awaitNoPendingTasks(client());
|
||||
boolean success = awaitBusy(() -> licensee.statuses.size() == 3);
|
||||
// trail license: enable, grace, disabled
|
||||
assertLicenseStates(licensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testTrialLicenseEnforcementMultipleLicensees() throws Exception {
|
||||
LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(5));
|
||||
licensesService.setGracePeriodDuration(TimeValue.timeValueSeconds(3));
|
||||
licensesService.start();
|
||||
String id1 = "testTrialLicenseEnforcementMultipleLicensees_1";
|
||||
final AssertingLicensee licensee1 = new AssertingLicensee(id1, logger);
|
||||
String id12 = "testTrialLicenseEnforcementMultipleLicensees_2";
|
||||
final AssertingLicensee licensee2 = new AssertingLicensee(id12, logger);
|
||||
awaitNoBlock(client());
|
||||
licensesService.register(licensee1);
|
||||
licensesService.register(licensee2);
|
||||
awaitNoPendingTasks(client());
|
||||
boolean success = awaitBusy(() -> licensee1.statuses.size() == 3);
|
||||
assertTrue(dumpLicensingStates(licensee1.statuses), success);
|
||||
success = awaitBusy(() -> licensee2.statuses.size() == 3);
|
||||
assertTrue(dumpLicensingStates(licensee2.statuses), success);
|
||||
// trail license: enable, grace, disabled
|
||||
assertLicenseStates(licensee1, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
assertLicenseStates(licensee2, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testTrialSignedLicenseEnforcement() throws Exception {
|
||||
LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(2));
|
||||
licensesService.setGracePeriodDuration(TimeValue.timeValueSeconds(3));
|
||||
licensesService.start();
|
||||
String id1 = "testTrialSignedLicenseEnforcement";
|
||||
final AssertingLicensee licensee = new AssertingLicensee(id1, logger);
|
||||
awaitNoBlock(client());
|
||||
licensesService.register(licensee);
|
||||
awaitNoPendingTasks(client());
|
||||
boolean success = awaitBusy(() -> licensee.statuses.size() == 1);
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
registerAndAckSignedLicenses(licensesService, generateSignedLicense(TimeValue.timeValueSeconds(4)), LicensesStatus.VALID);
|
||||
success = awaitBusy(() -> licensee.statuses.size() == 4);
|
||||
// trial: enable, signed: enable, signed: grace, signed: disabled
|
||||
assertLicenseStates(licensee, LicenseState.ENABLED, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testSignedLicenseEnforcement() throws Exception {
|
||||
LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(4));
|
||||
licensesService.setGracePeriodDuration(TimeValue.timeValueSeconds(3));
|
||||
licensesService.start();
|
||||
String id1 = "testSignedLicenseEnforcement";
|
||||
final AssertingLicensee licensee = new AssertingLicensee(id1, logger);
|
||||
awaitNoBlock(client());
|
||||
registerAndAckSignedLicenses(licensesService, generateSignedLicense(TimeValue.timeValueSeconds(2)), LicensesStatus.VALID);
|
||||
licensesService.register(licensee);
|
||||
awaitNoPendingTasks(client());
|
||||
boolean success = awaitBusy(() -> licensee.statuses.size() == 3);
|
||||
// signed: enable, signed: grace, signed: disabled
|
||||
assertLicenseStates(licensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testSingedLicenseEnforcementMultipleLicensees() throws Exception {
|
||||
LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(4));
|
||||
licensesService.setGracePeriodDuration(TimeValue.timeValueSeconds(3));
|
||||
licensesService.start();
|
||||
String id1 = "testSingedLicenseEnforcementMultipleLicensees_1";
|
||||
final AssertingLicensee licensee1 = new AssertingLicensee(id1, logger);
|
||||
String id12 = "testSingedLicenseEnforcementMultipleLicensees_2";
|
||||
final AssertingLicensee licensee2 = new AssertingLicensee(id12, logger);
|
||||
awaitNoBlock(client());
|
||||
registerAndAckSignedLicenses(licensesService, generateSignedLicense(TimeValue.timeValueSeconds(2)), LicensesStatus.VALID);
|
||||
licensesService.register(licensee1);
|
||||
licensesService.register(licensee2);
|
||||
awaitNoPendingTasks(client());
|
||||
boolean success = awaitBusy(() -> licensee1.statuses.size() == 3);
|
||||
assertTrue(dumpLicensingStates(licensee1.statuses), success);
|
||||
success = awaitBusy(() -> licensee2.statuses.size() == 3);
|
||||
assertTrue(dumpLicensingStates(licensee2.statuses), success);
|
||||
// signed license: enable, grace, disabled
|
||||
assertLicenseStates(licensee1, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
assertLicenseStates(licensee2, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testMultipleSignedLicenseEnforcement() throws Exception {
|
||||
// register with trial license and assert onEnable and onDisable notification
|
||||
LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(4));
|
||||
licensesService.setGracePeriodDuration(TimeValue.timeValueSeconds(1));
|
||||
licensesService.start();
|
||||
String id1 = "testMultipleSignedLicenseEnforcement";
|
||||
final AssertingLicensee licensee = new AssertingLicensee(id1, logger);
|
||||
awaitNoBlock(client());
|
||||
licensesService.register(licensee);
|
||||
awaitNoPendingTasks(client());
|
||||
// trial license enabled
|
||||
boolean success = awaitBusy(() -> licensee.statuses.size() == 1);
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
registerAndAckSignedLicenses(licensesService, generateSignedLicense("basic", TimeValue.timeValueSeconds(3)), LicensesStatus.VALID);
|
||||
// signed license enabled
|
||||
success = awaitBusy(() -> licensee.statuses.size() == 2);
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
registerAndAckSignedLicenses(licensesService, generateSignedLicense("gold", TimeValue.timeValueSeconds(2)), LicensesStatus.VALID);
|
||||
// second signed license enabled, grace and expired
|
||||
success = awaitBusy(() ->licensee.statuses.size() == 5);
|
||||
assertLicenseStates(licensee, LicenseState.ENABLED, LicenseState.ENABLED, LicenseState.ENABLED, LicenseState.GRACE_PERIOD,
|
||||
LicenseState.DISABLED);
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
public void testNonOverlappingMultipleLicensesEnforcement() throws Exception {
|
||||
// register with trial license and assert onEnable and onDisable notification
|
||||
LicensesService licensesService = getInstanceFromNode(LicensesService.class);
|
||||
licensesService.setTrialLicenseDuration(TimeValue.timeValueSeconds(3));
|
||||
licensesService.setGracePeriodDuration(TimeValue.timeValueSeconds(1));
|
||||
licensesService.start();
|
||||
String id1 = "testNonOverlappingMultipleLicensesEnforcement";
|
||||
final AssertingLicensee licensee = new AssertingLicensee(id1, logger);
|
||||
awaitNoBlock(client());
|
||||
licensesService.register(licensee);
|
||||
// trial license: enabled, grace, disabled
|
||||
boolean success = awaitBusy(() -> licensee.statuses.size() == 3);
|
||||
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
// install license
|
||||
registerAndAckSignedLicenses(licensesService, generateSignedLicense("basic", TimeValue.timeValueSeconds(2)), LicensesStatus.VALID);
|
||||
// trial license: enabled, grace, disabled, signed license: enabled, grace, disabled
|
||||
success = awaitBusy(() -> licensee.statuses.size() == 6);
|
||||
assertLicenseStates(licensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED, LicenseState.ENABLED,
|
||||
LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
assertTrue(dumpLicensingStates(licensee.statuses), success);
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
private void assertLicenseStates(AssertingLicensee licensee, LicenseState... states) {
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append("Actual: ");
|
||||
msg.append(dumpLicensingStates(licensee.statuses));
|
||||
msg.append(" Expected: ");
|
||||
msg.append(dumpLicensingStates(states));
|
||||
assertThat(msg.toString(), licensee.statuses.size(), equalTo(states.length));
|
||||
for (int i = 0; i < states.length; i++) {
|
||||
assertThat(msg.toString(), licensee.statuses.get(i).getLicenseState(), equalTo(states[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private String dumpLicensingStates(List<Licensee.Status> statuses) {
|
||||
return dumpLicensingStates(statuses.toArray(new Licensee.Status[statuses.size()]));
|
||||
}
|
||||
|
||||
private String dumpLicensingStates(Licensee.Status... statuses) {
|
||||
LicenseState[] states = new LicenseState[statuses.length];
|
||||
for (int i = 0; i < statuses.length; i++) {
|
||||
states[i] = statuses[i].getLicenseState();
|
||||
}
|
||||
return dumpLicensingStates(states);
|
||||
}
|
||||
|
||||
private String dumpLicensingStates(LicenseState... states) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
for (int i = 0; i < states.length; i++) {
|
||||
sb.append(states[i].name());
|
||||
if (i != states.length - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -7,14 +7,22 @@ package org.elasticsearch.license.plugin.core;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.cluster.service.ClusterService;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.xpack.graph.Graph;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.TestUtils;
|
||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
|
||||
import org.elasticsearch.xpack.monitoring.Monitoring;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.xpack.security.Security;
|
||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.xpack.watcher.Watcher;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@ -26,8 +34,19 @@ import static org.hamcrest.Matchers.not;
|
||||
|
||||
public class LicensesManagerServiceTests extends ESSingleNodeTestCase {
|
||||
|
||||
static {
|
||||
MetaData.registerPrototype(LicensesMetaData.TYPE, LicensesMetaData.PROTO);
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> getPlugins() {
|
||||
return Collections.singletonList(XPackPlugin.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings() {
|
||||
return Settings.builder().
|
||||
put(XPackPlugin.featureEnabledSetting(Security.NAME), false)
|
||||
.put(XPackPlugin.featureEnabledSetting(Monitoring.NAME), false)
|
||||
.put(XPackPlugin.featureEnabledSetting(Watcher.NAME), false)
|
||||
.put(XPackPlugin.featureEnabledSetting(Graph.NAME), false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -90,9 +109,7 @@ public class LicensesManagerServiceTests extends ESSingleNodeTestCase {
|
||||
|
||||
// ensure that the invalid license never made it to cluster state
|
||||
LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
|
||||
if (licensesMetaData != null) {
|
||||
assertThat(licensesMetaData.getLicense(), equalTo(LicensesMetaData.LICENSE_TOMBSTONE));
|
||||
}
|
||||
assertThat(licensesMetaData.getLicense(), not(equalTo(tamperedLicense)));
|
||||
}
|
||||
|
||||
public void testRemoveLicenses() throws Exception {
|
||||
@ -185,4 +202,4 @@ public class LicensesManagerServiceTests extends ESSingleNodeTestCase {
|
||||
}
|
||||
assertThat("remove license(s) failed", success.get(), equalTo(true));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.TestUtils;
|
||||
import org.elasticsearch.license.plugin.TestUtils.AssertingLicensee;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class LicensesNotificationTests extends AbstractLicenseServiceTestCase {
|
||||
|
||||
public void testLicenseNotification() throws Exception {
|
||||
final License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(48));
|
||||
setInitialState(license);
|
||||
licensesService.start();
|
||||
int nLicensee = randomIntBetween(1, 3);
|
||||
AssertingLicensee[] assertingLicensees = new AssertingLicensee[nLicensee];
|
||||
for (int i = 0; i < assertingLicensees.length; i++) {
|
||||
assertingLicensees[i] = new AssertingLicensee("testLicenseNotification" + i, logger);
|
||||
licensesService.register(assertingLicensees[i]);
|
||||
assertLicenseStates(assertingLicensees[i], LicenseState.ENABLED);
|
||||
}
|
||||
clock.fastForward(TimeValue.timeValueMillis(license.expiryDate() - clock.millis()));
|
||||
final LicensesMetaData licensesMetaData = new LicensesMetaData(license);
|
||||
licensesService.onUpdate(licensesMetaData);
|
||||
for (AssertingLicensee assertingLicensee : assertingLicensees) {
|
||||
assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD);
|
||||
}
|
||||
clock.fastForward(TimeValue.timeValueMillis((license.expiryDate() +
|
||||
LicensesService.GRACE_PERIOD_DURATION.getMillis()) - clock.millis()));
|
||||
licensesService.onUpdate(licensesMetaData);
|
||||
for (AssertingLicensee assertingLicensee : assertingLicensees) {
|
||||
assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
|
||||
}
|
||||
clock.setTime(new DateTime(DateTimeZone.UTC));
|
||||
final License newLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(2));
|
||||
clock.fastForward(TimeValue.timeValueHours(1));
|
||||
licensesService.onUpdate(new LicensesMetaData(newLicense));
|
||||
for (AssertingLicensee assertingLicensee : assertingLicensees) {
|
||||
assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED,
|
||||
LicenseState.ENABLED);
|
||||
}
|
||||
licensesService.stop();
|
||||
}
|
||||
|
||||
private void assertLicenseStates(AssertingLicensee licensee, LicenseState... states) {
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append("Actual: ");
|
||||
msg.append(dumpLicensingStates(licensee.statuses));
|
||||
msg.append(" Expected: ");
|
||||
msg.append(dumpLicensingStates(states));
|
||||
assertThat(msg.toString(), licensee.statuses.size(), equalTo(states.length));
|
||||
for (int i = 0; i < states.length; i++) {
|
||||
assertThat(msg.toString(), licensee.statuses.get(i).getLicenseState(), equalTo(states[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private String dumpLicensingStates(List<Licensee.Status> statuses) {
|
||||
return dumpLicensingStates(statuses.toArray(new Licensee.Status[statuses.size()]));
|
||||
}
|
||||
|
||||
private String dumpLicensingStates(Licensee.Status... statuses) {
|
||||
LicenseState[] states = new LicenseState[statuses.length];
|
||||
for (int i = 0; i < statuses.length; i++) {
|
||||
states[i] = statuses[i].getLicenseState();
|
||||
}
|
||||
return dumpLicensingStates(states);
|
||||
}
|
||||
|
||||
private String dumpLicensingStates(LicenseState... states) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
for (int i = 0; i < states.length; i++) {
|
||||
sb.append(states[i].name());
|
||||
if (i != states.length - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -252,7 +252,7 @@ public abstract class AbstractCollectorTestCase extends MonitoringIntegTestCase
|
||||
private volatile License license;
|
||||
|
||||
@Override
|
||||
public List<String> licenseesWithState(LicenseState state) {
|
||||
public LicenseState licenseState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -150,12 +150,8 @@ public class LicenseIntegrationTests extends MonitoringIntegTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> licenseesWithState(LicenseState state) {
|
||||
List<String> licenseesWithState = new ArrayList<>();
|
||||
for (Licensee licensee : licensees) {
|
||||
licenseesWithState.add(licensee.id());
|
||||
}
|
||||
return licenseesWithState;
|
||||
public LicenseState licenseState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user