Skip to content

Require acknowledgement to start_trial license #30135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion x-pack/docs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ buildRestTests.expectedUnconvertedCandidates = [
'en/watcher/trigger/schedule/yearly.asciidoc',
'en/watcher/troubleshooting.asciidoc',
'en/rest-api/license/delete-license.asciidoc',
'en/rest-api/license/start-trial.asciidoc',
'en/rest-api/license/update-license.asciidoc',
'en/ml/api-quickref.asciidoc',
'en/rest-api/ml/delete-calendar-event.asciidoc',
Expand Down
25 changes: 23 additions & 2 deletions x-pack/docs/en/rest-api/license/start-trial.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The following example checks whether you are eligible to start a trial:

[source,js]
------------------------------------------------------------
POST _xpack/license/start_trial
GET _xpack/license/start_trial
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make start_trial start with an underscore like other APIs that perform an action (_search, _authenticate, etc), so _start_trial and _start_basic

Copy link
Contributor Author

@Tim-Brooks Tim-Brooks Apr 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was discussed. Also I imagine that this "bug fix" PR to add necessary ack messages to a response would not be the place to modify a route that already exists in prior versions of ES x-pack.

------------------------------------------------------------
// CONSOLE
// TEST[skip:license testing issues]
Expand All @@ -49,6 +49,27 @@ Example response:
[source,js]
------------------------------------------------------------
{
"trial_was_started": true
"eligible_to_start_trial": true
}
------------------------------------------------------------
// NOTCONSOLE

The following example starts a 30-day trial license. The acknowledge
parameter is required as you are initiating a license that will expire.

[source,js]
------------------------------------------------------------
POST _xpack/license/start_trial?acknowledge=true
------------------------------------------------------------
// CONSOLE
// TEST[skip:license testing issues]

Example response:
[source,js]
------------------------------------------------------------
{
"trial_was_started": true,
"acknowledged": true
}
------------------------------------------------------------
// NOTCONSOLE
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ public void deleteLicense(DeleteLicenseRequest request, ActionListener<DeleteLic
client.execute(DeleteLicenseAction.INSTANCE, request, listener);
}

public PostStartTrialRequestBuilder preparePostUpgradeToTrial() {
public PostStartTrialRequestBuilder preparePostStartTrial() {
return new PostStartTrialRequestBuilder(client, PostStartTrialAction.INSTANCE);
}

public GetTrialStatusRequestBuilder prepareGetUpgradeToTrial() {
public GetTrialStatusRequestBuilder prepareGetStartTrial() {
return new GetTrialStatusRequestBuilder(client, GetTrialStatusAction.INSTANCE);
}

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

public class PostStartTrialRequest extends MasterNodeRequest<PostStartTrialRequest> {

private boolean acknowledge = false;
private String type;

@Override
Expand All @@ -31,25 +32,47 @@ public String getType() {
return type;
}

public PostStartTrialRequest acknowledge(boolean acknowledge) {
this.acknowledge = acknowledge;
return this;
}

public boolean isAcknowledged() {
return acknowledge;
}

@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
if (in.getVersion().onOrAfter(Version.V_6_3_0)) {
type = in.readString();
acknowledge = in.readBoolean();
} else {
type = "trial";
acknowledge = true;
}
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
Version version = Version.V_6_3_0;
// TODO: Change to 6.3 after backport
Version version = Version.V_7_0_0_alpha1;
if (out.getVersion().onOrAfter(version)) {
super.writeTo(out);
out.writeString(type);
out.writeBoolean(acknowledge);
} else {
throw new IllegalArgumentException("All nodes in cluster must be version [" + version
+ "] or newer to use `type` parameter. Attempting to write to node with version [" + out.getVersion() + "].");
if ("trial".equals(type) == false) {
throw new IllegalArgumentException("All nodes in cluster must be version [" + version
+ "] or newer to start trial with a different type than 'trial'. Attempting to write to " +
"a node with version [" + out.getVersion() + "] with trial type [" + type + "].");
} else if (acknowledge == false) {
throw new IllegalArgumentException("Request must be acknowledged to send to a node with a version " +
"prior to [" + version + "]. Attempting to send request to node with version [" + out.getVersion() + "] " +
"without acknowledgement.");
} else {
super.writeTo(out);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ class PostStartTrialRequestBuilder extends ActionRequestBuilder<PostStartTrialRe
PostStartTrialRequestBuilder(ElasticsearchClient client, PostStartTrialAction action) {
super(client, action, new PostStartTrialRequest());
}

public PostStartTrialRequestBuilder setAcknowledge(boolean acknowledge) {
request.acknowledge(acknowledge);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,33 @@
*/
package org.elasticsearch.license;

import org.elasticsearch.Version;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.rest.RestStatus;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

class PostStartTrialResponse extends ActionResponse {

// Nodes Prior to 6.3 did not have NEED_ACKNOWLEDGEMENT as part of status
enum Pre63Status {
UPGRADED_TO_TRIAL,
TRIAL_ALREADY_ACTIVATED;
}
enum Status {
UPGRADED_TO_TRIAL(true, null, RestStatus.OK),
TRIAL_ALREADY_ACTIVATED(false, "Operation failed: Trial was already activated.", RestStatus.FORBIDDEN);
TRIAL_ALREADY_ACTIVATED(false, "Operation failed: Trial was already activated.", RestStatus.FORBIDDEN),
NEED_ACKNOWLEDGEMENT(false,"Operation failed: Needs acknowledgement.", RestStatus.OK);

private final boolean isTrialStarted;

private final String errorMessage;
private final RestStatus restStatus;

Status(boolean isTrialStarted, String errorMessage, RestStatus restStatus) {
this.isTrialStarted = isTrialStarted;
this.errorMessage = errorMessage;
Expand All @@ -39,15 +49,24 @@ String getErrorMessage() {
RestStatus getRestStatus() {
return restStatus;
}

}

private Status status;
private Map<String, String[]> acknowledgeMessages;
private String acknowledgeMessage;

PostStartTrialResponse() {
}

PostStartTrialResponse(Status status) {
this(status, Collections.emptyMap(), null);
}

PostStartTrialResponse(Status status, Map<String, String[]> acknowledgeMessages, String acknowledgeMessage) {
this.status = status;
this.acknowledgeMessages = acknowledgeMessages;
this.acknowledgeMessage = acknowledgeMessage;
}

public Status getStatus() {
Expand All @@ -57,10 +76,58 @@ public Status getStatus() {
@Override
public void readFrom(StreamInput in) throws IOException {
status = in.readEnum(Status.class);
// TODO: Change to 6.3 after backport
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
acknowledgeMessage = in.readOptionalString();
int size = in.readVInt();
Map<String, String[]> acknowledgeMessages = new HashMap<>(size);
for (int i = 0; i < size; i++) {
String feature = in.readString();
int nMessages = in.readVInt();
String[] messages = new String[nMessages];
for (int j = 0; j < nMessages; j++) {
messages[j] = in.readString();
}
acknowledgeMessages.put(feature, messages);
}
this.acknowledgeMessages = acknowledgeMessages;
} else {
this.acknowledgeMessages = Collections.emptyMap();
}
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeEnum(status);
// TODO: Change to 6.3 after backport
Version version = Version.V_7_0_0_alpha1;
if (out.getVersion().onOrAfter(version)) {
out.writeEnum(status);
out.writeOptionalString(acknowledgeMessage);
out.writeVInt(acknowledgeMessages.size());
for (Map.Entry<String, String[]> entry : acknowledgeMessages.entrySet()) {
out.writeString(entry.getKey());
out.writeVInt(entry.getValue().length);
for (String message : entry.getValue()) {
out.writeString(message);
}
}
} else {
if (status == Status.UPGRADED_TO_TRIAL) {
out.writeEnum(Pre63Status.UPGRADED_TO_TRIAL);
} else if (status == Status.TRIAL_ALREADY_ACTIVATED) {
out.writeEnum(Pre63Status.TRIAL_ALREADY_ACTIVATED);
} else {
throw new IllegalArgumentException("Starting trial on node with version [" + Version.CURRENT + "] requires " +
"acknowledgement parameter.");
}
}
}

Map<String, String[]> getAcknowledgementMessages() {
return acknowledgeMessages;
}

String getAcknowledgementMessage() {
return acknowledgeMessage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class RestGetTrialStatus extends XPackRestHandler {

@Override
protected RestChannelConsumer doPrepareRequest(RestRequest request, XPackClient client) throws IOException {
return channel -> client.licensing().prepareGetUpgradeToTrial().execute(
return channel -> client.licensing().prepareGetStartTrial().execute(
new RestBuilderListener<GetTrialStatusResponse>(channel) {
@Override
public RestResponse buildResponse(GetTrialStatusResponse response, XContentBuilder builder) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.elasticsearch.xpack.core.rest.XPackRestHandler;

import java.io.IOException;
import java.util.Map;

import static org.elasticsearch.rest.RestRequest.Method.POST;

Expand All @@ -30,23 +31,36 @@ public class RestPostStartTrialLicense extends XPackRestHandler {
protected RestChannelConsumer doPrepareRequest(RestRequest request, XPackClient client) throws IOException {
PostStartTrialRequest startTrialRequest = new PostStartTrialRequest();
startTrialRequest.setType(request.param("type", "trial"));
startTrialRequest.acknowledge(request.paramAsBoolean("acknowledge", false));
return channel -> client.licensing().postStartTrial(startTrialRequest,
new RestBuilderListener<PostStartTrialResponse>(channel) {
@Override
public RestResponse buildResponse(PostStartTrialResponse response, XContentBuilder builder) throws Exception {
PostStartTrialResponse.Status status = response.getStatus();
builder.startObject();
builder.field("acknowledged", startTrialRequest.isAcknowledged());
if (status.isTrialStarted()) {
builder.startObject()
.field("trial_was_started", true)
.field("type", startTrialRequest.getType())
.endObject();
builder.field("trial_was_started", true);
builder.field("type", startTrialRequest.getType());
} else {
builder.startObject()
.field("trial_was_started", false)
.field("error_message", status.getErrorMessage())
.endObject();
builder.field("trial_was_started", false);
builder.field("error_message", status.getErrorMessage());
}

Map<String, String[]> acknowledgementMessages = response.getAcknowledgementMessages();
if (acknowledgementMessages.isEmpty() == false) {
builder.startObject("acknowledge");
builder.field("message", response.getAcknowledgementMessage());
for (Map.Entry<String, String[]> entry : acknowledgementMessages.entrySet()) {
builder.startArray(entry.getKey());
for (String message : entry.getValue()) {
builder.value(message);
}
builder.endArray();
}
builder.endObject();
}
builder.endObject();
return new BytesRestResponse(status.getRestStatus(), builder);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,23 @@
import org.elasticsearch.common.Nullable;

import java.time.Clock;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

public class StartTrialClusterTask extends ClusterStateUpdateTask {

private static final String ACKNOWLEDGEMENT_HEADER = "This API initiates a free 30-day trial for all platinum features. " +
"By starting this trial, you agree that it is subject to the terms and conditions at" +
" https://www.elastic.co/legal/trial_license/. To begin your free trial, call /start_trial again and specify " +
"the \"acknowledge=true\" parameter.";

private static final Map<String, String[]> ACK_MESSAGES = Collections.singletonMap("security",
new String[] {"With a trial license, X-Pack security features are available, but are not enabled by default."});

private final Logger logger;
private final String clusterName;
private final PostStartTrialRequest request;
Expand All @@ -39,7 +52,10 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS
LicensesMetaData oldLicensesMetaData = oldState.metaData().custom(LicensesMetaData.TYPE);
logger.debug("started self generated trial license: {}", oldLicensesMetaData);

if (oldLicensesMetaData == null || oldLicensesMetaData.isEligibleForTrial()) {
if (request.isAcknowledged() == false) {
listener.onResponse(new PostStartTrialResponse(PostStartTrialResponse.Status.NEED_ACKNOWLEDGEMENT,
ACK_MESSAGES, ACKNOWLEDGEMENT_HEADER));
} else if (oldLicensesMetaData == null || oldLicensesMetaData.isEligibleForTrial()) {
listener.onResponse(new PostStartTrialResponse(PostStartTrialResponse.Status.UPGRADED_TO_TRIAL));
} else {
listener.onResponse(new PostStartTrialResponse(PostStartTrialResponse.Status.TRIAL_ALREADY_ACTIVATED));
Expand All @@ -50,7 +66,9 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS
public ClusterState execute(ClusterState currentState) throws Exception {
LicensesMetaData currentLicensesMetaData = currentState.metaData().custom(LicensesMetaData.TYPE);

if (currentLicensesMetaData == null || currentLicensesMetaData.isEligibleForTrial()) {
if (request.isAcknowledged() == false) {
return currentState;
} else if (currentLicensesMetaData == null || currentLicensesMetaData.isEligibleForTrial()) {
long issueDate = clock.millis();
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
long expiryDate = issueDate + LicenseService.NON_BASIC_SELF_GENERATED_LICENSE_DURATION.getMillis();
Expand Down
Loading