Add type parameter to start_trial api (elastic/x-pack-elasticsearch#4102)
This is related to elastic/x-pack-elasticsearch#3877. This commit adds a paramer type to the start_trial api. This parameter allows the user to pass a type (trial, gold, or platinum) of license that will be generated. No matter what type is choosen, you can only generate one per major version. Original commit: elastic/x-pack-elasticsearch@b42234cbb5
This commit is contained in:
parent
3c82f24637
commit
498c110073
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.license;
|
||||
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
|
@ -13,11 +12,9 @@ import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
|
|||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.ClusterStateListener;
|
||||
import org.elasticsearch.cluster.ClusterStateUpdateTask;
|
||||
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.cluster.service.ClusterService;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.component.Lifecycle;
|
||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
||||
|
@ -36,12 +33,14 @@ import org.elasticsearch.xpack.core.scheduler.SchedulerEngine;
|
|||
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
|
@ -65,6 +64,8 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
|
|||
// pkg private for tests
|
||||
static final TimeValue NON_BASIC_SELF_GENERATED_LICENSE_DURATION = TimeValue.timeValueHours(30 * 24);
|
||||
|
||||
static final Set<String> VALID_TRIAL_TYPES = new HashSet<>(Arrays.asList("trial", "platinum", "gold"));
|
||||
|
||||
/**
|
||||
* Duration of grace period after a license has expired
|
||||
*/
|
||||
|
@ -305,52 +306,13 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
|
|||
return license == LicensesMetaData.LICENSE_TOMBSTONE ? null : license;
|
||||
}
|
||||
|
||||
void startSelfGeneratedTrialLicense(final ActionListener<PostStartTrialResponse> listener) {
|
||||
clusterService.submitStateUpdateTask("started self generated trial license",
|
||||
new ClusterStateUpdateTask() {
|
||||
@Override
|
||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
||||
LicensesMetaData licensesMetaData = oldState.metaData().custom(LicensesMetaData.TYPE);
|
||||
logger.debug("started self generated trial license: {}", licensesMetaData);
|
||||
|
||||
if (licensesMetaData == null || licensesMetaData.isEligibleForTrial()) {
|
||||
listener.onResponse(new PostStartTrialResponse(PostStartTrialResponse.STATUS.UPGRADED_TO_TRIAL));
|
||||
} else {
|
||||
listener.onResponse(new PostStartTrialResponse(PostStartTrialResponse.STATUS.TRIAL_ALREADY_ACTIVATED));
|
||||
void startTrialLicense(PostStartTrialRequest request, final ActionListener<PostStartTrialResponse> listener) {
|
||||
if (VALID_TRIAL_TYPES.contains(request.getType()) == false) {
|
||||
throw new IllegalArgumentException("Cannot start trial of type [" + request.getType() + "]. Valid trial types are "
|
||||
+ VALID_TRIAL_TYPES + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
||||
LicensesMetaData currentLicensesMetaData = currentState.metaData().custom(LicensesMetaData.TYPE);
|
||||
|
||||
if (currentLicensesMetaData == null || currentLicensesMetaData.isEligibleForTrial()) {
|
||||
long issueDate = clock.millis();
|
||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
||||
long expiryDate = issueDate + NON_BASIC_SELF_GENERATED_LICENSE_DURATION.getMillis();
|
||||
|
||||
License.Builder specBuilder = License.builder()
|
||||
.uid(UUID.randomUUID().toString())
|
||||
.issuedTo(clusterService.getClusterName().value())
|
||||
.maxNodes(SELF_GENERATED_LICENSE_MAX_NODES)
|
||||
.issueDate(issueDate)
|
||||
.type("trial")
|
||||
.expiryDate(expiryDate);
|
||||
License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder);
|
||||
LicensesMetaData newLicensesMetaData = new LicensesMetaData(selfGeneratedLicense, Version.CURRENT);
|
||||
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
|
||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
||||
} else {
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String source, @Nullable Exception e) {
|
||||
logger.error(new ParameterizedMessage("unexpected failure during [{}]", source), e);
|
||||
listener.onFailure(e);
|
||||
}
|
||||
});
|
||||
StartTrialClusterTask task = new StartTrialClusterTask(logger, clusterService.getClusterName().value(), clock, request, listener);
|
||||
clusterService.submitStateUpdateTask("started trial license", task);
|
||||
}
|
||||
|
||||
void startBasicLicense(PostStartBasicRequest request, final ActionListener<PostStartBasicResponse> listener) {
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.license;
|
|||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
|
||||
public class LicensingClient {
|
||||
|
||||
|
@ -48,7 +49,7 @@ public class LicensingClient {
|
|||
return new GetTrialStatusRequestBuilder(client, GetTrialStatusAction.INSTANCE);
|
||||
}
|
||||
|
||||
public void postUpgradeToTrial(PostStartTrialRequest request, ActionListener<PostStartTrialResponse> listener) {
|
||||
public void postStartTrial(PostStartTrialRequest request, ActionListener<PostStartTrialResponse> listener) {
|
||||
client.execute(PostStartTrialAction.INSTANCE, request, listener);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,13 +5,53 @@
|
|||
*/
|
||||
package org.elasticsearch.license;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.support.master.MasterNodeRequest;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PostStartTrialRequest extends MasterNodeRequest<PostStartTrialRequest> {
|
||||
|
||||
private String type;
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public PostStartTrialRequest setType(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
// TODO: Change to 6.3 after backport
|
||||
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
|
||||
type = in.readString();
|
||||
} else {
|
||||
type = "trial";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
// TODO: Change to 6.3 after backport
|
||||
Version version = Version.V_7_0_0_alpha1;
|
||||
if (out.getVersion().onOrAfter(version)) {
|
||||
out.writeString(type);
|
||||
} 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() + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,32 +8,55 @@ package org.elasticsearch.license;
|
|||
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;
|
||||
|
||||
class PostStartTrialResponse extends ActionResponse {
|
||||
|
||||
enum STATUS {
|
||||
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);
|
||||
|
||||
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;
|
||||
this.restStatus = restStatus;
|
||||
}
|
||||
|
||||
private STATUS status;
|
||||
boolean isTrialStarted() {
|
||||
return isTrialStarted;
|
||||
}
|
||||
|
||||
String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
RestStatus getRestStatus() {
|
||||
return restStatus;
|
||||
}
|
||||
}
|
||||
|
||||
private Status status;
|
||||
|
||||
PostStartTrialResponse() {
|
||||
}
|
||||
|
||||
PostStartTrialResponse(STATUS status) {
|
||||
PostStartTrialResponse(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public STATUS getStatus() {
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
status = in.readEnum(STATUS.class);
|
||||
status = in.readEnum(Status.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,7 +11,6 @@ import org.elasticsearch.rest.BytesRestResponse;
|
|||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
import org.elasticsearch.xpack.core.XPackClient;
|
||||
import org.elasticsearch.xpack.core.rest.XPackRestHandler;
|
||||
|
@ -29,23 +28,26 @@ public class RestPostStartTrialLicense extends XPackRestHandler {
|
|||
|
||||
@Override
|
||||
protected RestChannelConsumer doPrepareRequest(RestRequest request, XPackClient client) throws IOException {
|
||||
return channel -> client.licensing().preparePostUpgradeToTrial().execute(
|
||||
PostStartTrialRequest startTrialRequest = new PostStartTrialRequest();
|
||||
startTrialRequest.setType(request.param("type", "trial"));
|
||||
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();
|
||||
if (status == PostStartTrialResponse.STATUS.TRIAL_ALREADY_ACTIVATED) {
|
||||
PostStartTrialResponse.Status status = response.getStatus();
|
||||
if (status.isTrialStarted()) {
|
||||
builder.startObject()
|
||||
.field("trial_was_started", true)
|
||||
.field("type", startTrialRequest.getType())
|
||||
.endObject();
|
||||
} else {
|
||||
builder.startObject()
|
||||
.field("trial_was_started", false)
|
||||
.field("error_message", "Operation failed: Trial was already activated.")
|
||||
.field("error_message", status.getErrorMessage())
|
||||
.endObject();
|
||||
return new BytesRestResponse(RestStatus.FORBIDDEN, builder);
|
||||
} else if (status == PostStartTrialResponse.STATUS.UPGRADED_TO_TRIAL) {
|
||||
builder.startObject().field("trial_was_started", true).endObject();
|
||||
return new BytesRestResponse(RestStatus.OK, builder);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected status for PostStartTrialResponse: [" + status + "]");
|
||||
|
||||
}
|
||||
return new BytesRestResponse(status.getRestStatus(), builder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.ClusterStateUpdateTask;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.UUID;
|
||||
|
||||
public class StartTrialClusterTask extends ClusterStateUpdateTask {
|
||||
|
||||
private final Logger logger;
|
||||
private final String clusterName;
|
||||
private final PostStartTrialRequest request;
|
||||
private final ActionListener<PostStartTrialResponse> listener;
|
||||
private final Clock clock;
|
||||
|
||||
StartTrialClusterTask(Logger logger, String clusterName, Clock clock, PostStartTrialRequest request,
|
||||
ActionListener<PostStartTrialResponse> listener) {
|
||||
this.logger = logger;
|
||||
this.clusterName = clusterName;
|
||||
this.request = request;
|
||||
this.listener = listener;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
||||
LicensesMetaData oldLicensesMetaData = oldState.metaData().custom(LicensesMetaData.TYPE);
|
||||
logger.debug("started self generated trial license: {}", oldLicensesMetaData);
|
||||
|
||||
if (oldLicensesMetaData == null || oldLicensesMetaData.isEligibleForTrial()) {
|
||||
listener.onResponse(new PostStartTrialResponse(PostStartTrialResponse.Status.UPGRADED_TO_TRIAL));
|
||||
} else {
|
||||
listener.onResponse(new PostStartTrialResponse(PostStartTrialResponse.Status.TRIAL_ALREADY_ACTIVATED));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
||||
LicensesMetaData currentLicensesMetaData = currentState.metaData().custom(LicensesMetaData.TYPE);
|
||||
|
||||
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();
|
||||
|
||||
License.Builder specBuilder = License.builder()
|
||||
.uid(UUID.randomUUID().toString())
|
||||
.issuedTo(clusterName)
|
||||
.maxNodes(LicenseService.SELF_GENERATED_LICENSE_MAX_NODES)
|
||||
.issueDate(issueDate)
|
||||
.type(request.getType())
|
||||
.expiryDate(expiryDate);
|
||||
License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder);
|
||||
LicensesMetaData newLicensesMetaData = new LicensesMetaData(selfGeneratedLicense, Version.CURRENT);
|
||||
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
|
||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
||||
} else {
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String source, @Nullable Exception e) {
|
||||
logger.error(new ParameterizedMessage("unexpected failure during [{}]", source), e);
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ public class TransportPostStartTrialAction extends TransportMasterNodeAction<Pos
|
|||
@Override
|
||||
protected void masterOperation(PostStartTrialRequest request, ClusterState state,
|
||||
ActionListener<PostStartTrialResponse> listener) throws Exception {
|
||||
licenseService.startSelfGeneratedTrialLicense(listener);
|
||||
licenseService.startTrialLicense(request, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -46,11 +46,9 @@ public class StartTrialLicenseTests extends AbstractLicensesIntegrationTestCase
|
|||
return Arrays.asList(XPackClientPlugin.class, Netty4Plugin.class);
|
||||
}
|
||||
|
||||
public void testUpgradeToTrial() throws Exception {
|
||||
public void testStartTrial() throws Exception {
|
||||
LicensingClient licensingClient = new LicensingClient(client());
|
||||
GetLicenseResponse getLicenseResponse = licensingClient.prepareGetLicense().get();
|
||||
|
||||
assertEquals("basic", getLicenseResponse.license().type());
|
||||
ensureStartingWithBasic();
|
||||
|
||||
RestClient restClient = getRestClient();
|
||||
Response response = restClient.performRequest("GET", "/_xpack/license/trial_status");
|
||||
|
@ -58,22 +56,58 @@ public class StartTrialLicenseTests extends AbstractLicensesIntegrationTestCase
|
|||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
assertEquals("{\"eligible_to_start_trial\":true}", body);
|
||||
|
||||
Response response2 = restClient.performRequest("POST", "/_xpack/license/start_trial");
|
||||
String type = randomFrom(LicenseService.VALID_TRIAL_TYPES);
|
||||
|
||||
Response response2 = restClient.performRequest("POST", "/_xpack/license/start_trial?type=" + type);
|
||||
String body2 = Streams.copyToString(new InputStreamReader(response2.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||
assertEquals(200, response2.getStatusLine().getStatusCode());
|
||||
assertEquals("{\"trial_was_started\":true}", body2);
|
||||
assertTrue(body2.contains("\"trial_was_started\":true"));
|
||||
assertTrue(body2.contains("\"type\":\"" + type + "\""));
|
||||
|
||||
assertBusy(() -> {
|
||||
GetLicenseResponse postTrialLicenseResponse = licensingClient.prepareGetLicense().get();
|
||||
assertEquals(type, postTrialLicenseResponse.license().type());
|
||||
});
|
||||
|
||||
Response response3 = restClient.performRequest("GET", "/_xpack/license/trial_status");
|
||||
String body3 = Streams.copyToString(new InputStreamReader(response3.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||
assertEquals(200, response3.getStatusLine().getStatusCode());
|
||||
assertEquals("{\"eligible_to_start_trial\":false}", body3);
|
||||
|
||||
String secondAttemptType = randomFrom(LicenseService.VALID_TRIAL_TYPES);
|
||||
|
||||
ResponseException ex = expectThrows(ResponseException.class,
|
||||
() -> restClient.performRequest("POST", "/_xpack/license/start_trial"));
|
||||
() -> restClient.performRequest("POST", "/_xpack/license/start_trial?type=" + secondAttemptType));
|
||||
Response response4 = ex.getResponse();
|
||||
String body4 = Streams.copyToString(new InputStreamReader(response4.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||
assertEquals(403, response4.getStatusLine().getStatusCode());
|
||||
assertTrue(body4.contains("\"trial_was_started\":false"));
|
||||
assertTrue(body4.contains("\"error_message\":\"Operation failed: Trial was already activated.\""));
|
||||
}
|
||||
|
||||
public void testInvalidType() throws Exception {
|
||||
ensureStartingWithBasic();
|
||||
|
||||
ResponseException ex = expectThrows(ResponseException.class, () ->
|
||||
getRestClient().performRequest("POST", "/_xpack/license/start_trial?type=basic"));
|
||||
Response response = ex.getResponse();
|
||||
String body = Streams.copyToString(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||
assertEquals(400, response.getStatusLine().getStatusCode());
|
||||
assertTrue(body.contains("\"type\":\"illegal_argument_exception\""));
|
||||
assertTrue(body.contains("\"reason\":\"Cannot start trial of type [basic]. Valid trial types are ["));
|
||||
}
|
||||
|
||||
private void ensureStartingWithBasic() throws Exception {
|
||||
LicensingClient licensingClient = new LicensingClient(client());
|
||||
GetLicenseResponse getLicenseResponse = licensingClient.prepareGetLicense().get();
|
||||
|
||||
if ("basic".equals(getLicenseResponse.license().type()) == false) {
|
||||
licensingClient.preparePostStartBasic().setAcknowledge(true).get();
|
||||
}
|
||||
|
||||
assertBusy(() -> {
|
||||
GetLicenseResponse postTrialLicenseResponse = licensingClient.prepareGetLicense().get();
|
||||
assertEquals("basic", postTrialLicenseResponse.license().type());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
"parts" : {
|
||||
},
|
||||
"params": {
|
||||
"type": {
|
||||
"type" : "string",
|
||||
"description" : "The type of trial license to generate (default: \"trial\")"
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": null
|
||||
|
|
|
@ -127,6 +127,12 @@ teardown:
|
|||
- match: { trial_was_started: false }
|
||||
- match: { error_message: "Operation failed: Trial was already activated." }
|
||||
---
|
||||
"Trial license cannot be basic":
|
||||
- do:
|
||||
catch: bad_request
|
||||
xpack.license.post_start_trial:
|
||||
type: "basic"
|
||||
---
|
||||
"Can start basic license if do not already have basic":
|
||||
- do:
|
||||
xpack.license.get_basic_status: {}
|
||||
|
|
Loading…
Reference in New Issue