Skip to content

Commit 230ad53

Browse files
HLRC: Add support for XPack Post Start Basic Licence API (elastic#33606)
Relates to elastic#29827
1 parent 936faba commit 230ad53

File tree

13 files changed

+693
-7
lines changed

13 files changed

+693
-7
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.apache.http.HttpEntity;
2323
import org.elasticsearch.action.ActionListener;
2424
import org.elasticsearch.action.support.master.AcknowledgedResponse;
25+
import org.elasticsearch.client.license.StartBasicRequest;
26+
import org.elasticsearch.client.license.StartBasicResponse;
2527
import org.elasticsearch.common.Strings;
2628
import org.elasticsearch.common.io.Streams;
2729
import org.elasticsearch.common.xcontent.DeprecationHandler;
@@ -121,6 +123,28 @@ public void deleteLicenseAsync(DeleteLicenseRequest request, RequestOptions opti
121123
AcknowledgedResponse::fromXContent, listener, emptySet());
122124
}
123125

126+
/**
127+
* Initiates an indefinite basic license.
128+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
129+
* @return the response
130+
* @throws IOException in case there is a problem sending the request or parsing back the response
131+
*/
132+
public StartBasicResponse startBasic(StartBasicRequest request, RequestOptions options) throws IOException {
133+
return restHighLevelClient.performRequestAndParseEntity(request, LicenseRequestConverters::startBasic, options,
134+
StartBasicResponse::fromXContent, emptySet());
135+
}
136+
137+
/**
138+
* Asynchronously initiates an indefinite basic license.
139+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
140+
* @param listener the listener to be notified upon request completion
141+
*/
142+
public void startBasicAsync(StartBasicRequest request, RequestOptions options,
143+
ActionListener<StartBasicResponse> listener) {
144+
restHighLevelClient.performRequestAsyncAndParseEntity(request, LicenseRequestConverters::startBasic, options,
145+
StartBasicResponse::fromXContent, listener, emptySet());
146+
}
147+
124148
/**
125149
* Converts an entire response into a json string
126150
*

client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseRequestConverters.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
import org.apache.http.client.methods.HttpDelete;
2323
import org.apache.http.client.methods.HttpGet;
24+
import org.apache.http.client.methods.HttpPost;
2425
import org.apache.http.client.methods.HttpPut;
26+
import org.elasticsearch.client.license.StartBasicRequest;
2527
import org.elasticsearch.protocol.xpack.license.DeleteLicenseRequest;
2628
import org.elasticsearch.protocol.xpack.license.GetLicenseRequest;
2729
import org.elasticsearch.protocol.xpack.license.PutLicenseRequest;
@@ -61,4 +63,18 @@ static Request deleteLicense(DeleteLicenseRequest deleteLicenseRequest) {
6163
parameters.withMasterTimeout(deleteLicenseRequest.masterNodeTimeout());
6264
return request;
6365
}
66+
67+
static Request startBasic(StartBasicRequest startBasicRequest) {
68+
String endpoint = new RequestConverters.EndpointBuilder()
69+
.addPathPartAsIs("_xpack", "license", "start_basic")
70+
.build();
71+
Request request = new Request(HttpPost.METHOD_NAME, endpoint);
72+
RequestConverters.Params parameters = new RequestConverters.Params(request);
73+
parameters.withTimeout(startBasicRequest.timeout());
74+
parameters.withMasterTimeout(startBasicRequest.masterNodeTimeout());
75+
if (startBasicRequest.isAcknowledge()) {
76+
parameters.putParam("acknowledge", "true");
77+
}
78+
return request;
79+
}
6480
}

client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -980,9 +980,11 @@ EndpointBuilder addCommaSeparatedPathParts(String[] parts) {
980980
return this;
981981
}
982982

983-
EndpointBuilder addPathPartAsIs(String part) {
984-
if (Strings.hasLength(part)) {
985-
joiner.add(part);
983+
EndpointBuilder addPathPartAsIs(String ... parts) {
984+
for (String part : parts) {
985+
if (Strings.hasLength(part)) {
986+
joiner.add(part);
987+
}
986988
}
987989
return this;
988990
}

client/rest-high-level/src/main/java/org/elasticsearch/client/TimedRequest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import org.elasticsearch.common.unit.TimeValue;
2222

23+
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
24+
2325
/**
2426
* A base request for any requests that supply timeouts.
2527
*
@@ -28,8 +30,11 @@
2830
*/
2931
public class TimedRequest implements Validatable {
3032

31-
private TimeValue timeout;
32-
private TimeValue masterTimeout;
33+
public static final TimeValue DEFAULT_ACK_TIMEOUT = timeValueSeconds(30);
34+
public static final TimeValue DEFAULT_MASTER_NODE_TIMEOUT = TimeValue.timeValueSeconds(30);
35+
36+
private TimeValue timeout = DEFAULT_ACK_TIMEOUT;
37+
private TimeValue masterTimeout = DEFAULT_MASTER_NODE_TIMEOUT;
3338

3439
public void setTimeout(TimeValue timeout) {
3540
this.timeout = timeout;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client.license;
20+
21+
import org.elasticsearch.client.TimedRequest;
22+
23+
public class StartBasicRequest extends TimedRequest {
24+
private final boolean acknowledge;
25+
26+
public StartBasicRequest() {
27+
this(false);
28+
}
29+
30+
public StartBasicRequest(boolean acknowledge) {
31+
this.acknowledge = acknowledge;
32+
}
33+
34+
public boolean isAcknowledge() {
35+
return acknowledge;
36+
}
37+
}
38+
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client.license;
20+
21+
import org.elasticsearch.common.ParseField;
22+
import org.elasticsearch.common.collect.Tuple;
23+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
24+
import org.elasticsearch.common.xcontent.XContentParseException;
25+
import org.elasticsearch.common.xcontent.XContentParser;
26+
import org.elasticsearch.rest.RestStatus;
27+
28+
import java.io.IOException;
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.HashMap;
32+
import java.util.List;
33+
import java.util.Map;
34+
import java.util.Objects;
35+
36+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
37+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
38+
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
39+
40+
public class StartBasicResponse {
41+
42+
private static final ConstructingObjectParser<StartBasicResponse, Void> PARSER = new ConstructingObjectParser<>(
43+
"start_basic_response", true, (a, v) -> {
44+
boolean basicWasStarted = (Boolean) a[0];
45+
String errorMessage = (String) a[1];
46+
47+
if (basicWasStarted) {
48+
return new StartBasicResponse(StartBasicResponse.Status.GENERATED_BASIC);
49+
}
50+
StartBasicResponse.Status status = StartBasicResponse.Status.fromErrorMessage(errorMessage);
51+
@SuppressWarnings("unchecked") Tuple<String, Map<String, String[]>> acknowledgements = (Tuple<String, Map<String, String[]>>) a[2];
52+
return new StartBasicResponse(status, acknowledgements.v2(), acknowledgements.v1());
53+
});
54+
55+
static {
56+
PARSER.declareBoolean(constructorArg(), new ParseField("basic_was_started"));
57+
PARSER.declareString(optionalConstructorArg(), new ParseField("error_message"));
58+
PARSER.declareObject(optionalConstructorArg(), (parser, v) -> {
59+
Map<String, String[]> acknowledgeMessages = new HashMap<>();
60+
String message = null;
61+
XContentParser.Token token;
62+
String currentFieldName = null;
63+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
64+
if (token == XContentParser.Token.FIELD_NAME) {
65+
currentFieldName = parser.currentName();
66+
} else {
67+
if (currentFieldName == null) {
68+
throw new XContentParseException(parser.getTokenLocation(), "expected message header or acknowledgement");
69+
}
70+
if (new ParseField("message").getPreferredName().equals(currentFieldName)) {
71+
ensureExpectedToken(XContentParser.Token.VALUE_STRING, token, parser::getTokenLocation);
72+
message = parser.text();
73+
} else {
74+
if (token != XContentParser.Token.START_ARRAY) {
75+
throw new XContentParseException(parser.getTokenLocation(), "unexpected acknowledgement type");
76+
}
77+
List<String> acknowledgeMessagesList = new ArrayList<>();
78+
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
79+
ensureExpectedToken(XContentParser.Token.VALUE_STRING, token, parser::getTokenLocation);
80+
acknowledgeMessagesList.add(parser.text());
81+
}
82+
acknowledgeMessages.put(currentFieldName, acknowledgeMessagesList.toArray(new String[0]));
83+
}
84+
}
85+
}
86+
return new Tuple<>(message, acknowledgeMessages);
87+
},
88+
new ParseField("acknowledge"));
89+
}
90+
91+
private Map<String, String[]> acknowledgeMessages;
92+
private String acknowledgeMessage;
93+
94+
enum Status {
95+
GENERATED_BASIC(true, null, RestStatus.OK),
96+
ALREADY_USING_BASIC(false, "Operation failed: Current license is basic.", RestStatus.FORBIDDEN),
97+
NEED_ACKNOWLEDGEMENT(false, "Operation failed: Needs acknowledgement.", RestStatus.OK);
98+
99+
private final boolean isBasicStarted;
100+
private final String errorMessage;
101+
private final RestStatus restStatus;
102+
103+
Status(boolean isBasicStarted, String errorMessage, RestStatus restStatus) {
104+
this.isBasicStarted = isBasicStarted;
105+
this.errorMessage = errorMessage;
106+
this.restStatus = restStatus;
107+
}
108+
109+
String getErrorMessage() {
110+
return errorMessage;
111+
}
112+
113+
boolean isBasicStarted() {
114+
return isBasicStarted;
115+
}
116+
117+
static StartBasicResponse.Status fromErrorMessage(final String errorMessage) {
118+
final StartBasicResponse.Status[] values = StartBasicResponse.Status.values();
119+
for (StartBasicResponse.Status status : values) {
120+
if (Objects.equals(status.errorMessage, errorMessage)) {
121+
return status;
122+
}
123+
}
124+
throw new IllegalArgumentException("No status for error message ['" + errorMessage + "']");
125+
}
126+
}
127+
128+
private StartBasicResponse.Status status;
129+
130+
public StartBasicResponse() {
131+
}
132+
133+
StartBasicResponse(StartBasicResponse.Status status) {
134+
this(status, Collections.emptyMap(), null);
135+
}
136+
137+
StartBasicResponse(StartBasicResponse.Status status,
138+
Map<String, String[]> acknowledgeMessages, String acknowledgeMessage) {
139+
this.status = status;
140+
this.acknowledgeMessages = acknowledgeMessages;
141+
this.acknowledgeMessage = acknowledgeMessage;
142+
}
143+
144+
public boolean isAcknowledged() {
145+
return status != StartBasicResponse.Status.NEED_ACKNOWLEDGEMENT;
146+
}
147+
148+
public boolean isBasicStarted() {
149+
return status.isBasicStarted;
150+
}
151+
152+
public String getErrorMessage() {
153+
return status.errorMessage;
154+
}
155+
156+
public String getAcknowledgeMessage() {
157+
return acknowledgeMessage;
158+
}
159+
160+
public Map<String, String[]> getAcknowledgeMessages() {
161+
return acknowledgeMessages;
162+
}
163+
164+
public static StartBasicResponse fromXContent(XContentParser parser) throws IOException {
165+
return PARSER.parse(parser, null);
166+
}
167+
168+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client;
20+
21+
import org.apache.http.client.methods.HttpPost;
22+
import org.elasticsearch.action.support.master.AcknowledgedRequest;
23+
import org.elasticsearch.client.license.StartBasicRequest;
24+
import org.elasticsearch.test.ESTestCase;
25+
26+
import java.util.HashMap;
27+
import java.util.Map;
28+
29+
import static org.elasticsearch.client.RequestConvertersTests.setRandomMasterTimeout;
30+
import static org.elasticsearch.client.RequestConvertersTests.setRandomTimeout;
31+
import static org.hamcrest.CoreMatchers.equalTo;
32+
import static org.hamcrest.CoreMatchers.is;
33+
import static org.hamcrest.CoreMatchers.nullValue;
34+
35+
public class LicenseRequestConvertersTests extends ESTestCase {
36+
public void testStartBasic() {
37+
final boolean acknowledge = randomBoolean();
38+
StartBasicRequest startBasicRequest = new StartBasicRequest(acknowledge);
39+
Map<String, String> expectedParams = new HashMap<>();
40+
if (acknowledge) {
41+
expectedParams.put("acknowledge", Boolean.TRUE.toString());
42+
}
43+
44+
setRandomTimeout(startBasicRequest, AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, expectedParams);
45+
setRandomMasterTimeout(startBasicRequest, expectedParams);
46+
Request request = LicenseRequestConverters.startBasic(startBasicRequest);
47+
48+
assertThat(request.getMethod(), equalTo(HttpPost.METHOD_NAME));
49+
assertThat(request.getEndpoint(), equalTo("/_xpack/license/start_basic"));
50+
assertThat(request.getParameters(), equalTo(expectedParams));
51+
assertThat(request.getEntity(), is(nullValue()));
52+
}
53+
}

0 commit comments

Comments
 (0)