HLRC: Add support for XPack Post Start Basic Licence API (#33606)

Relates to #29827
This commit is contained in:
Vladimir Dolzhenko 2018-10-16 15:21:30 +02:00 committed by GitHub
parent 936faba6c4
commit 230ad5339b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 693 additions and 7 deletions

View File

@ -22,6 +22,8 @@ package org.elasticsearch.client;
import org.apache.http.HttpEntity;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.license.StartBasicRequest;
import org.elasticsearch.client.license.StartBasicResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.xcontent.DeprecationHandler;
@ -121,6 +123,28 @@ public final class LicenseClient {
AcknowledgedResponse::fromXContent, listener, emptySet());
}
/**
* Initiates an indefinite basic license.
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public StartBasicResponse startBasic(StartBasicRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request, LicenseRequestConverters::startBasic, options,
StartBasicResponse::fromXContent, emptySet());
}
/**
* Asynchronously initiates an indefinite basic license.
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener the listener to be notified upon request completion
*/
public void startBasicAsync(StartBasicRequest request, RequestOptions options,
ActionListener<StartBasicResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(request, LicenseRequestConverters::startBasic, options,
StartBasicResponse::fromXContent, listener, emptySet());
}
/**
* Converts an entire response into a json string
*

View File

@ -21,7 +21,9 @@ package org.elasticsearch.client;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.elasticsearch.client.license.StartBasicRequest;
import org.elasticsearch.protocol.xpack.license.DeleteLicenseRequest;
import org.elasticsearch.protocol.xpack.license.GetLicenseRequest;
import org.elasticsearch.protocol.xpack.license.PutLicenseRequest;
@ -61,4 +63,18 @@ public class LicenseRequestConverters {
parameters.withMasterTimeout(deleteLicenseRequest.masterNodeTimeout());
return request;
}
static Request startBasic(StartBasicRequest startBasicRequest) {
String endpoint = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_xpack", "license", "start_basic")
.build();
Request request = new Request(HttpPost.METHOD_NAME, endpoint);
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withTimeout(startBasicRequest.timeout());
parameters.withMasterTimeout(startBasicRequest.masterNodeTimeout());
if (startBasicRequest.isAcknowledge()) {
parameters.putParam("acknowledge", "true");
}
return request;
}
}

View File

@ -980,9 +980,11 @@ final class RequestConverters {
return this;
}
EndpointBuilder addPathPartAsIs(String part) {
if (Strings.hasLength(part)) {
joiner.add(part);
EndpointBuilder addPathPartAsIs(String ... parts) {
for (String part : parts) {
if (Strings.hasLength(part)) {
joiner.add(part);
}
}
return this;
}

View File

@ -20,6 +20,8 @@ package org.elasticsearch.client;
import org.elasticsearch.common.unit.TimeValue;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
/**
* A base request for any requests that supply timeouts.
*
@ -28,8 +30,11 @@ import org.elasticsearch.common.unit.TimeValue;
*/
public class TimedRequest implements Validatable {
private TimeValue timeout;
private TimeValue masterTimeout;
public static final TimeValue DEFAULT_ACK_TIMEOUT = timeValueSeconds(30);
public static final TimeValue DEFAULT_MASTER_NODE_TIMEOUT = TimeValue.timeValueSeconds(30);
private TimeValue timeout = DEFAULT_ACK_TIMEOUT;
private TimeValue masterTimeout = DEFAULT_MASTER_NODE_TIMEOUT;
public void setTimeout(TimeValue timeout) {
this.timeout = timeout;

View File

@ -0,0 +1,38 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.license;
import org.elasticsearch.client.TimedRequest;
public class StartBasicRequest extends TimedRequest {
private final boolean acknowledge;
public StartBasicRequest() {
this(false);
}
public StartBasicRequest(boolean acknowledge) {
this.acknowledge = acknowledge;
}
public boolean isAcknowledge() {
return acknowledge;
}
}

View File

@ -0,0 +1,168 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.license;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentParseException;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
public class StartBasicResponse {
private static final ConstructingObjectParser<StartBasicResponse, Void> PARSER = new ConstructingObjectParser<>(
"start_basic_response", true, (a, v) -> {
boolean basicWasStarted = (Boolean) a[0];
String errorMessage = (String) a[1];
if (basicWasStarted) {
return new StartBasicResponse(StartBasicResponse.Status.GENERATED_BASIC);
}
StartBasicResponse.Status status = StartBasicResponse.Status.fromErrorMessage(errorMessage);
@SuppressWarnings("unchecked") Tuple<String, Map<String, String[]>> acknowledgements = (Tuple<String, Map<String, String[]>>) a[2];
return new StartBasicResponse(status, acknowledgements.v2(), acknowledgements.v1());
});
static {
PARSER.declareBoolean(constructorArg(), new ParseField("basic_was_started"));
PARSER.declareString(optionalConstructorArg(), new ParseField("error_message"));
PARSER.declareObject(optionalConstructorArg(), (parser, v) -> {
Map<String, String[]> acknowledgeMessages = new HashMap<>();
String message = null;
XContentParser.Token token;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else {
if (currentFieldName == null) {
throw new XContentParseException(parser.getTokenLocation(), "expected message header or acknowledgement");
}
if (new ParseField("message").getPreferredName().equals(currentFieldName)) {
ensureExpectedToken(XContentParser.Token.VALUE_STRING, token, parser::getTokenLocation);
message = parser.text();
} else {
if (token != XContentParser.Token.START_ARRAY) {
throw new XContentParseException(parser.getTokenLocation(), "unexpected acknowledgement type");
}
List<String> acknowledgeMessagesList = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
ensureExpectedToken(XContentParser.Token.VALUE_STRING, token, parser::getTokenLocation);
acknowledgeMessagesList.add(parser.text());
}
acknowledgeMessages.put(currentFieldName, acknowledgeMessagesList.toArray(new String[0]));
}
}
}
return new Tuple<>(message, acknowledgeMessages);
},
new ParseField("acknowledge"));
}
private Map<String, String[]> acknowledgeMessages;
private String acknowledgeMessage;
enum Status {
GENERATED_BASIC(true, null, RestStatus.OK),
ALREADY_USING_BASIC(false, "Operation failed: Current license is basic.", RestStatus.FORBIDDEN),
NEED_ACKNOWLEDGEMENT(false, "Operation failed: Needs acknowledgement.", RestStatus.OK);
private final boolean isBasicStarted;
private final String errorMessage;
private final RestStatus restStatus;
Status(boolean isBasicStarted, String errorMessage, RestStatus restStatus) {
this.isBasicStarted = isBasicStarted;
this.errorMessage = errorMessage;
this.restStatus = restStatus;
}
String getErrorMessage() {
return errorMessage;
}
boolean isBasicStarted() {
return isBasicStarted;
}
static StartBasicResponse.Status fromErrorMessage(final String errorMessage) {
final StartBasicResponse.Status[] values = StartBasicResponse.Status.values();
for (StartBasicResponse.Status status : values) {
if (Objects.equals(status.errorMessage, errorMessage)) {
return status;
}
}
throw new IllegalArgumentException("No status for error message ['" + errorMessage + "']");
}
}
private StartBasicResponse.Status status;
public StartBasicResponse() {
}
StartBasicResponse(StartBasicResponse.Status status) {
this(status, Collections.emptyMap(), null);
}
StartBasicResponse(StartBasicResponse.Status status,
Map<String, String[]> acknowledgeMessages, String acknowledgeMessage) {
this.status = status;
this.acknowledgeMessages = acknowledgeMessages;
this.acknowledgeMessage = acknowledgeMessage;
}
public boolean isAcknowledged() {
return status != StartBasicResponse.Status.NEED_ACKNOWLEDGEMENT;
}
public boolean isBasicStarted() {
return status.isBasicStarted;
}
public String getErrorMessage() {
return status.errorMessage;
}
public String getAcknowledgeMessage() {
return acknowledgeMessage;
}
public Map<String, String[]> getAcknowledgeMessages() {
return acknowledgeMessages;
}
public static StartBasicResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
}

View File

@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client;
import org.apache.http.client.methods.HttpPost;
import org.elasticsearch.action.support.master.AcknowledgedRequest;
import org.elasticsearch.client.license.StartBasicRequest;
import org.elasticsearch.test.ESTestCase;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.client.RequestConvertersTests.setRandomMasterTimeout;
import static org.elasticsearch.client.RequestConvertersTests.setRandomTimeout;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
public class LicenseRequestConvertersTests extends ESTestCase {
public void testStartBasic() {
final boolean acknowledge = randomBoolean();
StartBasicRequest startBasicRequest = new StartBasicRequest(acknowledge);
Map<String, String> expectedParams = new HashMap<>();
if (acknowledge) {
expectedParams.put("acknowledge", Boolean.TRUE.toString());
}
setRandomTimeout(startBasicRequest, AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, expectedParams);
setRandomMasterTimeout(startBasicRequest, expectedParams);
Request request = LicenseRequestConverters.startBasic(startBasicRequest);
assertThat(request.getMethod(), equalTo(HttpPost.METHOD_NAME));
assertThat(request.getEndpoint(), equalTo("/_xpack/license/start_basic"));
assertThat(request.getParameters(), equalTo(expectedParams));
assertThat(request.getEntity(), is(nullValue()));
}
}

View File

@ -0,0 +1,130 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client;
import org.elasticsearch.Build;
import org.elasticsearch.client.license.StartBasicRequest;
import org.elasticsearch.client.license.StartBasicResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.protocol.xpack.license.LicensesStatus;
import org.elasticsearch.protocol.xpack.license.PutLicenseRequest;
import org.elasticsearch.protocol.xpack.license.PutLicenseResponse;
import org.junit.After;
import org.junit.BeforeClass;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.empty;
public class LicensingIT extends ESRestHighLevelClientTestCase {
@BeforeClass
public static void checkForSnapshot() {
assumeTrue("Trial license used to rollback is only valid when tested against snapshot/test builds",
Build.CURRENT.isSnapshot());
}
@After
public void rollbackToTrial() throws IOException {
putTrialLicense();
}
public static void putTrialLicense() throws IOException {
assumeTrue("Trial license is only valid when tested against snapshot/test builds",
Build.CURRENT.isSnapshot());
// use a hard-coded trial license for 20 yrs to be able to roll back from another licenses
final String licenseDefinition = Strings.toString(jsonBuilder()
.startObject()
.field("licenses", Arrays.asList(
MapBuilder.<String, Object>newMapBuilder()
.put("uid", "96fc37c6-6fc9-43e2-a40d-73143850cd72")
.put("type", "trial")
// 2018-10-16 07:02:48 UTC
.put("issue_date_in_millis", "1539673368158")
// 2038-10-11 07:02:48 UTC, 20 yrs later
.put("expiry_date_in_millis", "2170393368158")
.put("max_nodes", "5")
.put("issued_to", "client_rest-high-level_integTestCluster")
.put("issuer", "elasticsearch")
.put("start_date_in_millis", "-1")
.put("signature",
"AAAABAAAAA3FXON9kGmNqmH+ASDWAAAAIAo5/x6hrsGh1GqqrJmy4qgmEC7gK0U4zQ6q5ZEMhm4jAAABAAcdKHL0BfM2uqTgT7BDuFxX5lb"
+ "t/bHDVJ421Wwgm5p3IMbw/W13iiAHz0hhDziF7acJbc/y65L+BKGtVC1gSSHeLDHaAD66VrjKxfc7VbGyJIAYBOdujf0rheurmaD3IcNo"
+ "/tWDjCdtTwrNziFkorsGcPadBP5Yc6csk3/Q74DlfiYweMBxLUfkBERwxwd5OQS6ujGvl/4bb8p5zXvOw8vMSaAXSXXnExP6lam+0934W"
+ "0kHvU7IGk+fCUjOaiSWKSoE4TEcAtVNYj/oRoRtfQ1KQGpdCHxTHs1BimdZaG0nBHDsvhYlVVLSvHN6QzqsHWgFDG6JJxhtU872oTRSUHA=")
.immutableMap()))
.endObject());
final PutLicenseRequest request = new PutLicenseRequest();
request.setAcknowledge(true);
request.setLicenseDefinition(licenseDefinition);
final PutLicenseResponse response = highLevelClient().license().putLicense(request, RequestOptions.DEFAULT);
assertThat(response.isAcknowledged(), equalTo(true));
assertThat(response.status(), equalTo(LicensesStatus.VALID));
}
public void testStartBasic() throws Exception {
// we don't test the case where we successfully start a basic because the integ test cluster generates one on startup
// and we don't have a good way to prevent that / work around it in this test project
// case where we don't acknowledge basic license conditions
{
final StartBasicRequest request = new StartBasicRequest();
final StartBasicResponse response = highLevelClient().license().startBasic(request, RequestOptions.DEFAULT);
assertThat(response.isAcknowledged(), equalTo(false));
assertThat(response.isBasicStarted(), equalTo(false));
assertThat(response.getErrorMessage(), equalTo("Operation failed: Needs acknowledgement."));
assertThat(response.getAcknowledgeMessage(),
containsString("This license update requires acknowledgement. " +
"To acknowledge the license, please read the following messages and call /start_basic again"));
assertNotEmptyAcknowledgeMessages(response);
}
// case where we acknowledge and the basic is started successfully
{
final StartBasicRequest request = new StartBasicRequest(true);
final StartBasicResponse response = highLevelClient().license().startBasic(request, RequestOptions.DEFAULT);
assertThat(response.isAcknowledged(), equalTo(true));
assertThat(response.isBasicStarted(), equalTo(true));
assertThat(response.getErrorMessage(), nullValue());
assertThat(response.getAcknowledgeMessage(), nullValue());
assertThat(response.getAcknowledgeMessages().size(), equalTo(0));
}
}
private static void assertNotEmptyAcknowledgeMessages(StartBasicResponse response) {
assertThat(response.getAcknowledgeMessages().entrySet(), not(empty()));
for (Map.Entry<String, String[]> entry : response.getAcknowledgeMessages().entrySet()) {
assertThat(entry.getKey(), not(isEmptyOrNullString()));
final List<String> messages = Arrays.asList(entry.getValue());
for (String message : messages) {
assertThat(message, not(isEmptyOrNullString()));
}
}
}
}

View File

@ -1572,6 +1572,12 @@ public class RequestConvertersTests extends ESTestCase {
setRandomLocal(request::local, expectedParams);
}
static void setRandomTimeout(TimedRequest request, TimeValue defaultTimeout, Map<String, String> expectedParams) {
setRandomTimeout(s ->
request.setTimeout(TimeValue.parseTimeValue(s, request.getClass().getName() + ".timeout")),
defaultTimeout, expectedParams);
}
static void setRandomTimeout(Consumer<String> setter, TimeValue defaultTimeout, Map<String, String> expectedParams) {
if (randomBoolean()) {
String timeout = randomTimeValue();
@ -1583,9 +1589,19 @@ public class RequestConvertersTests extends ESTestCase {
}
static void setRandomMasterTimeout(MasterNodeRequest<?> request, Map<String, String> expectedParams) {
setRandomMasterTimeout(request::masterNodeTimeout, expectedParams);
}
static void setRandomMasterTimeout(TimedRequest request, Map<String, String> expectedParams) {
setRandomMasterTimeout(s ->
request.setMasterTimeout(TimeValue.parseTimeValue(s, request.getClass().getName() + ".masterNodeTimeout")),
expectedParams);
}
static void setRandomMasterTimeout(Consumer<String> setter, Map<String, String> expectedParams) {
if (randomBoolean()) {
String masterTimeout = randomTimeValue();
request.masterNodeTimeout(masterTimeout);
setter.accept(masterTimeout);
expectedParams.put("master_timeout", masterTimeout);
} else {
expectedParams.put("master_timeout", MasterNodeRequest.DEFAULT_MASTER_NODE_TIMEOUT.getStringRep());

View File

@ -26,6 +26,8 @@ import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.license.StartBasicRequest;
import org.elasticsearch.client.license.StartBasicResponse;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.protocol.xpack.license.DeleteLicenseRequest;
import org.elasticsearch.protocol.xpack.license.GetLicenseRequest;
@ -33,11 +35,15 @@ import org.elasticsearch.protocol.xpack.license.GetLicenseResponse;
import org.elasticsearch.protocol.xpack.license.LicensesStatus;
import org.elasticsearch.protocol.xpack.license.PutLicenseRequest;
import org.elasticsearch.protocol.xpack.license.PutLicenseResponse;
import org.junit.After;
import org.junit.BeforeClass;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.client.LicensingIT.putTrialLicense;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasSize;
@ -50,8 +56,18 @@ import static org.hamcrest.Matchers.startsWith;
*/
public class LicensingDocumentationIT extends ESRestHighLevelClientTestCase {
@BeforeClass
public static void checkForSnapshot() {
assumeTrue("Trial license used to rollback is only valid when tested against snapshot/test builds",
Build.CURRENT.isSnapshot());
}
@After
public void rollbackToTrial() throws IOException {
putTrialLicense();
}
public void testLicense() throws Exception {
assumeTrue("License is only valid when tested against snapshot/test builds", Build.CURRENT.isSnapshot());
RestHighLevelClient client = highLevelClient();
String license = "{\"license\": {\"uid\":\"893361dc-9749-4997-93cb-802e3d7fa4a8\",\"type\":\"gold\"," +
"\"issue_date_in_millis\":1411948800000,\"expiry_date_in_millis\":1914278399999,\"max_nodes\":1,\"issued_to\":\"issued_to\"," +
@ -215,4 +231,50 @@ public class LicensingDocumentationIT extends ESRestHighLevelClientTestCase {
assertThat(currentLicense, endsWith("}"));
}
}
public void testPostStartBasic() throws Exception {
RestHighLevelClient client = highLevelClient();
{
//tag::start-basic-execute
StartBasicRequest request = new StartBasicRequest();
StartBasicResponse response = client.license().startBasic(request, RequestOptions.DEFAULT);
//end::start-basic-execute
//tag::start-basic-response
boolean acknowledged = response.isAcknowledged(); // <1>
boolean basicStarted = response.isBasicStarted(); // <2>
String errorMessage = response.getErrorMessage(); // <3>
String acknowledgeMessage = response.getAcknowledgeMessage(); // <4>
Map<String, String[]> acknowledgeMessages = response.getAcknowledgeMessages(); // <5>
//end::start-basic-response
}
{
StartBasicRequest request = new StartBasicRequest();
// tag::start-basic-listener
ActionListener<StartBasicResponse> listener = new ActionListener<StartBasicResponse>() {
@Override
public void onResponse(StartBasicResponse indexResponse) {
// <1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
// end::start-basic-listener
// Replace the empty listener by a blocking listener in test
final CountDownLatch latch = new CountDownLatch(1);
listener = new LatchedActionListener<>(listener, latch);
// tag::start-basic-execute-async
client.license().startBasicAsync(
request, RequestOptions.DEFAULT, listener); // <1>
// end::start-basic-execute-async
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.license;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.protocol.xpack.common.ProtocolUtils;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.CoreMatchers.equalTo;
public class StartBasicResponseTests extends ESTestCase {
public void testFromXContent() throws Exception {
StartBasicResponse.Status status = randomFrom(StartBasicResponse.Status.values());
boolean acknowledged = status != StartBasicResponse.Status.NEED_ACKNOWLEDGEMENT;
String acknowledgeMessage = null;
Map<String, String[]> ackMessages = Collections.emptyMap();
if (status != StartBasicResponse.Status.GENERATED_BASIC) {
acknowledgeMessage = randomAlphaOfLength(10);
ackMessages = randomAckMessages();
}
final StartBasicResponse startBasicResponse = new StartBasicResponse(status, ackMessages, acknowledgeMessage);
XContentType xContentType = randomFrom(XContentType.values());
XContentBuilder builder = XContentFactory.contentBuilder(xContentType);
toXContent(startBasicResponse, builder);
final StartBasicResponse response = StartBasicResponse.fromXContent(createParser(builder));
assertThat(response.isAcknowledged(), equalTo(acknowledged));
assertThat(response.isBasicStarted(), equalTo(status.isBasicStarted()));
assertThat(response.getAcknowledgeMessage(), equalTo(acknowledgeMessage));
assertThat(ProtocolUtils.equals(response.getAcknowledgeMessages(), ackMessages), equalTo(true));
}
private static void toXContent(StartBasicResponse response, XContentBuilder builder) throws IOException {
builder.startObject();
builder.field("acknowledged", response.isAcknowledged());
if (response.isBasicStarted()) {
builder.field("basic_was_started", true);
} else {
builder.field("basic_was_started", false);
builder.field("error_message", response.getErrorMessage());
}
if (response.getAcknowledgeMessages().isEmpty() == false) {
builder.startObject("acknowledge");
builder.field("message", response.getAcknowledgeMessage());
for (Map.Entry<String, String[]> entry : response.getAcknowledgeMessages().entrySet()) {
builder.startArray(entry.getKey());
for (String message : entry.getValue()) {
builder.value(message);
}
builder.endArray();
}
builder.endObject();
}
builder.endObject();
}
private static Map<String, String[]> randomAckMessages() {
int nFeatures = randomIntBetween(1, 5);
Map<String, String[]> ackMessages = new HashMap<>();
for (int i = 0; i < nFeatures; i++) {
String feature = randomAlphaOfLengthBetween(9, 15);
int nMessages = randomIntBetween(1, 5);
String[] messages = new String[nMessages];
for (int j = 0; j < nMessages; j++) {
messages[j] = randomAlphaOfLengthBetween(10, 30);
}
ackMessages.put(feature, messages);
}
return ackMessages;
}
}

View File

@ -0,0 +1,67 @@
[[java-rest-high-start-basic]]
=== Start Basic License
[[java-rest-high-start-basic-execution]]
==== Execution
This API creates and enables a basic license using the `startBasic()` method.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/LicensingDocumentationIT.java[start-basic-execute]
--------------------------------------------------
[[java-rest-high-start-basic-response]]
==== Response
The returned `StartBasicResponse` returns a field indicating whether the
basic was started. If it was started, the response returns a the type of
license started. If it was not started, it returns an error message describing
why.
Acknowledgement messages may also be returned if this API was called without
the `acknowledge` flag set to `true`. In this case you need to display the
messages to the end user and if they agree, resubmit the request with the
`acknowledge` flag set to `true`. Please note that the response will still
return a 200 return code even if it requires an acknowledgement. So, it is
necessary to check the `acknowledged` flag.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/LicensingDocumentationIT.java[start-basic-response]
--------------------------------------------------
<1> Whether or not the request had the `acknowledge` flag set
<2> Whether or not this request caused a basic to start
<3> If this request did not cause a basic to start, a message explaining why
<4> If the user's request did not have the `acknowledge` flag set, a summary
of the user's acknowledgement required for this API
<5> If the user's request did not have the `acknowledge` flag set, contains
keys of commercial features and values of messages describing how they will
be affected by licensing changes as the result of starting a basic
[[java-rest-high-start-basic-async]]
==== Asynchronous Execution
This request can be executed asynchronously:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/LicensingDocumentationIT.java[start-basic-execute-async]
--------------------------------------------------
<1> The `StartBasicResponse` to execute and the `ActionListener` to use when
the execution completes
The asynchronous method does not block and returns immediately. Once it is
completed the `ActionListener` is called back using the `onResponse` method
if the execution successfully completed or using the `onFailure` method if
it failed.
A typical listener for `StartBasicResponse` looks like:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/LicensingDocumentationIT.java[start-basic-listener]
--------------------------------------------------
<1> Called when the execution is successfully completed. The response is
provided as an argument
<2> Called in case of failure. The raised exception is provided as an argument

View File

@ -214,10 +214,12 @@ The Java High Level REST Client supports the following Licensing APIs:
* <<java-rest-high-put-license>>
* <<java-rest-high-get-license>>
* <<java-rest-high-delete-license>>
* <<java-rest-high-start-basic>>
include::licensing/put-license.asciidoc[]
include::licensing/get-license.asciidoc[]
include::licensing/delete-license.asciidoc[]
include::licensing/start-basic.asciidoc[]
== Machine Learning APIs
:upid: {mainid}-x-pack-ml