diff --git a/pom.xml b/pom.xml index 70de30e5f29..78b89079d91 100644 --- a/pom.xml +++ b/pom.xml @@ -10,11 +10,18 @@ 1.4.0-SNAPSHOT + 4.10.0 + + org.apache.lucene + lucene-test-framework + ${lucene.version} + test + org.elasticsearch elasticsearch @@ -22,13 +29,24 @@ test test-jar - + + org.hamcrest + hamcrest-all + 1.3 + test + junit junit 4.11 test + + com.carrotsearch.randomizedtesting + randomizedtesting-runner + 2.1.2 + test + @@ -147,6 +165,121 @@ + + com.carrotsearch.randomizedtesting + junit4-maven-plugin + 2.1.2 + + + tests + test + + junit4 + + + 20 + pipe,warn + true + + + + + + + + + + + ${tests.jvms} + + + + + + + **/*Tests.class + **/*Test.class + + + **/Abstract*.class + **/*StressTest.class + + + -Xmx${tests.heap.size} + -Xms${tests.heap.size} + -Xss256k + -XX:MaxPermSize=128m + -XX:MaxDirectMemorySize=512m + -Des.logger.prefix= + -XX:+HeapDumpOnOutOfMemoryError + -XX:HeapDumpPath=${tests.heapdump.path} + + ${tests.shuffle} + ${tests.verbose} + ${tests.seed} + ${tests.failfast} + false + + . + + + ${tests.bwc} + ${tests.bwc.path} + ${tests.bwc.version} + ${tests.jvm.argline} + ${tests.processors} + ${tests.appendseed} + ${tests.iters} + ${tests.maxfailures} + ${tests.failfast} + ${tests.class} + ${tests.method} + ${tests.nightly} + ${tests.verbose} + ${tests.badapples} + ${tests.weekly} + ${tests.slow} + ${tests.awaitsfix} + ${tests.slow} + ${tests.timeoutSuite} + ${tests.showSuccess} + ${tests.integration} + ${tests.client.ratio} + ${tests.enable_mock_modules} + ${tests.assertion.disabled} + ${tests.rest} + ${tests.rest.suite} + ${tests.rest.blacklist} + ${tests.rest.spec} + ${tests.network} + ${tests.cluster} + ${tests.heap.size} + ${tests.filter} + ${project.version} + ${env.ES_TEST_LOCAL} + ${es.node.mode} + ${es.logger.level} + ${tests.security.manager} + ${tests.compatibility} + true + + ${project.build.directory} + ${basedir}/dev-tools/tests.policy + + + + + diff --git a/src/main/java/org/elasticsearch/license/core/LicenseBuilders.java b/src/main/java/org/elasticsearch/license/core/LicenseBuilders.java index 1799e8e883e..091169b4046 100644 --- a/src/main/java/org/elasticsearch/license/core/LicenseBuilders.java +++ b/src/main/java/org/elasticsearch/license/core/LicenseBuilders.java @@ -50,7 +50,7 @@ public class LicenseBuilders { } public static class LicensesBuilder { - private Map licenseMap; + private Map licenseMap = new HashMap<>(); public LicensesBuilder() { } @@ -60,11 +60,15 @@ public class LicenseBuilders { } public LicensesBuilder license(ESLicense license) { - initLicenses(); putIfAppropriate(license); return this; } + public LicensesBuilder licenseAsIs(ESLicense license) { + licenseMap.put(license.feature(), license); + return this; + } + public LicensesBuilder licenses(Collection licenses) { for (ESLicense esLicense : licenses) { license(esLicense); @@ -100,12 +104,6 @@ public class LicenseBuilders { }; } - private void initLicenses() { - if (licenseMap == null) { - licenseMap = new HashMap<>(); - } - } - /** * Add a {@link org.elasticsearch.license.core.ESLicenses.ESLicense} to * {@link org.elasticsearch.license.core.ESLicenses} only if diff --git a/src/main/java/org/elasticsearch/license/manager/Utils.java b/src/main/java/org/elasticsearch/license/manager/Utils.java index d62f734e29e..2b22c6b4b6d 100644 --- a/src/main/java/org/elasticsearch/license/manager/Utils.java +++ b/src/main/java/org/elasticsearch/license/manager/Utils.java @@ -23,7 +23,7 @@ public class Utils { private Utils() { } - static ESLicenses getESLicensesFromSignatures(final LicenseManager licenseManager, Set signatures) { + public static ESLicenses getESLicensesFromSignatures(final LicenseManager licenseManager, Set signatures) { final LicenseBuilders.LicensesBuilder licensesBuilder = LicenseBuilders.licensesBuilder(); for (String signature : signatures) { licensesBuilder.license(getESLicenseFromSignature(licenseManager, signature)); diff --git a/src/main/java/org/elasticsearch/license/plugin/LicensePlugin.java b/src/main/java/org/elasticsearch/license/plugin/LicensePlugin.java index a5751220557..bc78792ab26 100644 --- a/src/main/java/org/elasticsearch/license/plugin/LicensePlugin.java +++ b/src/main/java/org/elasticsearch/license/plugin/LicensePlugin.java @@ -6,10 +6,12 @@ package org.elasticsearch.license.plugin; import org.elasticsearch.action.ActionModule; +import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.license.plugin.action.get.GetLicenseAction; import org.elasticsearch.license.plugin.action.get.TransportGetLicenseAction; import org.elasticsearch.license.plugin.action.put.PutLicenseAction; import org.elasticsearch.license.plugin.action.put.TransportPutLicenseAction; +import org.elasticsearch.license.plugin.cluster.LicensesMetaData; import org.elasticsearch.license.plugin.rest.RestGetLicenseAction; import org.elasticsearch.license.plugin.rest.RestPutLicenseAction; import org.elasticsearch.plugins.AbstractPlugin; @@ -18,6 +20,10 @@ import org.elasticsearch.rest.RestModule; //TODO: plugin hooks public class LicensePlugin extends AbstractPlugin { + static { + MetaData.registerFactory(LicensesMetaData.TYPE, LicensesMetaData.FACTORY); + } + @Override public String name() { return "license"; @@ -38,4 +44,6 @@ public class LicensePlugin extends AbstractPlugin { module.registerAction(PutLicenseAction.INSTANCE, TransportPutLicenseAction.class); module.registerAction(GetLicenseAction.INSTANCE, TransportGetLicenseAction.class); } + + //TODO: module binding? (LicenseModule) } diff --git a/src/main/java/org/elasticsearch/license/plugin/Utils.java b/src/main/java/org/elasticsearch/license/plugin/Utils.java new file mode 100644 index 00000000000..c9a438e3f41 --- /dev/null +++ b/src/main/java/org/elasticsearch/license/plugin/Utils.java @@ -0,0 +1,9 @@ +/* + * 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; + +public class Utils { +} diff --git a/src/main/java/org/elasticsearch/license/plugin/action/Utils.java b/src/main/java/org/elasticsearch/license/plugin/action/Utils.java new file mode 100644 index 00000000000..0c1d0114b74 --- /dev/null +++ b/src/main/java/org/elasticsearch/license/plugin/action/Utils.java @@ -0,0 +1,86 @@ +/* + * 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.action; + +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.license.core.ESLicenses; +import org.elasticsearch.license.core.LicenseBuilders; + +import java.io.IOException; +import java.util.Map; + +import static org.elasticsearch.license.core.ESLicenses.*; + +public class Utils { + + + public static ESLicenses readLicensesFrom(StreamInput in) throws IOException { + final LicenseBuilders.LicensesBuilder licensesBuilder = LicenseBuilders.licensesBuilder(); + boolean exists = in.readBoolean(); + if (exists) { + int size = in.readVInt(); + for (int i = 0; i < size; i++) { + licensesBuilder.licenseAsIs(licenseFromMap(in.readMap())); + } + } + return licensesBuilder.build(); + } + + public static void writeLicensesTo(ESLicenses esLicenses, StreamOutput out) throws IOException { + if (esLicenses == null) { + out.writeBoolean(false); + return; + } + out.writeBoolean(true); + out.writeVInt(esLicenses.licenses().size()); + for (ESLicense esLicense : esLicenses) { + out.writeMap(licenseAsMap(esLicense)); + } + } + + public static Map licenseAsMap(ESLicense esLicense) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put(LicenseFields.UID, esLicense.uid()); + builder.put(LicenseFields.TYPE, esLicense.type()); + builder.put(LicenseFields.SUBSCRIPTION_TYPE, esLicense.subscriptionType()); + builder.put(LicenseFields.ISSUE_DATE, esLicense.issueDate()); + builder.put(LicenseFields.FEATURE, esLicense.feature()); + builder.put(LicenseFields.EXPIRY_DATE, esLicense.expiryDate()); + builder.put(LicenseFields.MAX_NODES, esLicense.maxNodes()); + builder.put(LicenseFields.ISSUED_TO, esLicense.issuedTo()); + builder.put(LicenseFields.SIGNATURE, esLicense.signature()); + return builder.build(); + } + + public static ESLicense licenseFromMap(Map map) { + return LicenseBuilders.licenseBuilder(false) + .uid((String) map.get(LicenseFields.UID)) + .type((Type) map.get(LicenseFields.TYPE)) + .subscriptionType((SubscriptionType) map.get(LicenseFields.SUBSCRIPTION_TYPE)) + .issueDate((Long) map.get(LicenseFields.ISSUE_DATE)) + .feature((FeatureType) map.get(LicenseFields.FEATURE)) + .expiryDate((Long) map.get(LicenseFields.EXPIRY_DATE)) + .maxNodes((Integer) map.get(LicenseFields.MAX_NODES)) + .issuedTo((String) map.get(LicenseFields.ISSUED_TO)) + .signature((String) map.get(LicenseFields.SIGNATURE)) + .build(); + + } + + final static class LicenseFields { + private final static String UID = "uid"; + private final static String TYPE = "type"; + private final static String SUBSCRIPTION_TYPE = "subscription_type"; + private final static String ISSUE_DATE = "issue_date"; + private final static String FEATURE = "feature"; + private final static String EXPIRY_DATE = "expiry_date"; + private final static String MAX_NODES = "max_nodes"; + private final static String ISSUED_TO = "issued_to"; + private final static String SIGNATURE = "signature"; + } +} diff --git a/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequest.java b/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequest.java index e73524b0a67..86674843d7a 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequest.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequest.java @@ -16,19 +16,12 @@ import java.io.IOException; public class GetLicenseRequest extends MasterNodeReadOperationRequest { - GetLicenseRequest() { + public GetLicenseRequest() { } - @Override public ActionRequestValidationException validate() { return null; - /*ActionRequestValidationException validationException = null; - if (repositories == null) { - validationException = addValidationError("repositories is null", validationException); - } - return validationException; - */ } @Override diff --git a/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequestBuilder.java b/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequestBuilder.java index e095eb672b5..dd5a022a7b9 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequestBuilder.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequestBuilder.java @@ -12,7 +12,7 @@ import org.elasticsearch.client.ClusterAdminClient; public class GetLicenseRequestBuilder extends MasterNodeReadOperationRequestBuilder { /** - * Creates new get repository request builder + * Creates new get licenses request builder * * @param clusterAdminClient cluster admin client */ diff --git a/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java b/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java index 1e9bf979efa..49ad1e0175c 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java @@ -6,71 +6,42 @@ package org.elasticsearch.license.plugin.action.get; import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.cluster.metadata.RepositoryMetaData; -import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.license.core.ESLicenses; +import org.elasticsearch.license.plugin.cluster.LicensesMetaData; import java.io.IOException; -import java.util.Iterator; -public class GetLicenseResponse extends ActionResponse implements Iterable { +import static org.elasticsearch.license.plugin.action.Utils.readLicensesFrom; +import static org.elasticsearch.license.plugin.action.Utils.writeLicensesTo; - //TODO: use LicenseMetaData instead - private ImmutableList repositories = ImmutableList.of(); +public class GetLicenseResponse extends ActionResponse { + private ESLicenses licenses = null; GetLicenseResponse() { } - GetLicenseResponse(ImmutableList repositories) { - this.repositories = repositories; + GetLicenseResponse(ESLicenses esLicenses) { + this.licenses = esLicenses; } - /** - * List of repositories to return - * - * @return list or repositories - */ - public ImmutableList repositories() { - return repositories; + public ESLicenses licenses() { + return licenses; } @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - int size = in.readVInt(); - ImmutableList.Builder repositoryListBuilder = ImmutableList.builder(); - for (int j = 0; j < size; j++) { - repositoryListBuilder.add(new RepositoryMetaData( - in.readString(), - in.readString(), - ImmutableSettings.readSettingsFromStream(in)) - ); - } - repositories = repositoryListBuilder.build(); + licenses = readLicensesFrom(in); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeVInt(repositories.size()); - for (RepositoryMetaData repository : repositories) { - out.writeString(repository.name()); - out.writeString(repository.type()); - ImmutableSettings.writeSettingsToStream(repository.settings(), out); - } + writeLicensesTo(licenses, out); } - /** - * Iterator over the repositories data - * - * @return iterator over the repositories data - */ - @Override - public Iterator iterator() { - return repositories.iterator(); - } } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/license/plugin/action/get/TransportGetLicenseAction.java b/src/main/java/org/elasticsearch/license/plugin/action/get/TransportGetLicenseAction.java index 0e74335a5fc..7f6836ffc27 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/get/TransportGetLicenseAction.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/get/TransportGetLicenseAction.java @@ -18,6 +18,7 @@ import org.elasticsearch.cluster.metadata.RepositoriesMetaData; import org.elasticsearch.cluster.metadata.RepositoryMetaData; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.plugin.cluster.LicensesMetaData; import org.elasticsearch.repositories.RepositoryMissingException; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; @@ -52,31 +53,8 @@ public class TransportGetLicenseAction extends TransportMasterNodeReadOperationA @Override protected void masterOperation(final GetLicenseRequest request, ClusterState state, final ActionListener listener) throws ElasticsearchException { - //TODO: impl after custom metadata impl - /* MetaData metaData = state.metaData(); - RepositoriesMetaData repositories = metaData.custom(RepositoriesMetaData.TYPE); - if (request.repositories().length == 0 || (request.repositories().length == 1 && "_all".equals(request.repositories()[0]))) { - if (repositories != null) { - listener.onResponse(new GetRepositoriesResponse(repositories.repositories())); - } else { - listener.onResponse(new GetRepositoriesResponse(ImmutableList.of())); - } - } else { - if (repositories != null) { - ImmutableList.Builder repositoryListBuilder = ImmutableList.builder(); - for (String repository : request.repositories()) { - RepositoryMetaData repositoryMetaData = repositories.repository(repository); - if (repositoryMetaData == null) { - listener.onFailure(new RepositoryMissingException(repository)); - return; - } - repositoryListBuilder.add(repositoryMetaData); - } - listener.onResponse(new GetRepositoriesResponse(repositoryListBuilder.build())); - } else { - listener.onFailure(new RepositoryMissingException(request.repositories()[0])); - } - }*/ + LicensesMetaData licenses = metaData.custom(LicensesMetaData.TYPE); + listener.onResponse(new GetLicenseResponse(licenses)); } } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java b/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java index c627965a4f8..6c3af1f20ba 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java @@ -15,6 +15,9 @@ import org.elasticsearch.license.core.LicenseUtils; import java.io.IOException; +import static org.elasticsearch.license.plugin.action.Utils.readLicensesFrom; +import static org.elasticsearch.license.plugin.action.Utils.writeLicensesTo; + public class PutLicenseRequest extends AcknowledgedRequest { private ESLicenses license; @@ -61,14 +64,14 @@ public class PutLicenseRequest extends AcknowledgedRequest { @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - license = LicenseUtils.readLicenseFromInputStream(in); + license = readLicensesFrom(in); readTimeout(in); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - LicenseUtils.dumpLicenseAsJson(license, out); + writeLicensesTo(license, out); writeTimeout(out); } } diff --git a/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java b/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java index 846a34a51f9..6b0b8c1449c 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java @@ -11,21 +11,24 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.plugin.service.LicensesService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; public class TransportPutLicenseAction extends TransportMasterNodeOperationAction { - //private final RepositoriesService repositoriesService + private final LicensesService licensesService; @Inject public TransportPutLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, - ThreadPool threadPool, ActionFilters actionFilters) { + LicensesService licensesService, ThreadPool threadPool, ActionFilters actionFilters) { super(settings, PutLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters); + this.licensesService = licensesService; } @@ -52,8 +55,20 @@ public class TransportPutLicenseAction extends TransportMasterNodeOperationActio @Override protected void masterOperation(final PutLicenseRequest request, ClusterState state, final ActionListener listener) throws ElasticsearchException { //TODO + licensesService.registerLicenses("put_licenses []",request, new ActionListener() { + @Override + public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) { + listener.onResponse(new PutLicenseResponse(clusterStateUpdateResponse.isAcknowledged())); + } + + @Override + public void onFailure(Throwable e) { + listener.onFailure(e); + } + }); + /* - repositoriesService.registerRepository(new RepositoriesService.RegisterRepositoryRequest("put_repository [" + request.name() + "]", request.name(), request.type()) + repositoriesService.registerLicenses(new RepositoriesService.RegisterRepositoryRequest("put_repository [" + request.name() + "]", request.name(), request.type()) .settings(request.settings()) .masterNodeTimeout(request.masterNodeTimeout()) .ackTimeout(request.timeout()), new ActionListener() { diff --git a/src/main/java/org/elasticsearch/license/plugin/cluster/LicensesMetaData.java b/src/main/java/org/elasticsearch/license/plugin/cluster/LicensesMetaData.java new file mode 100644 index 00000000000..01fd12b158c --- /dev/null +++ b/src/main/java/org/elasticsearch/license/plugin/cluster/LicensesMetaData.java @@ -0,0 +1,173 @@ +/* + * 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.cluster; + +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.license.core.ESLicenses; + +import java.io.IOException; +import java.util.*; + +import static org.elasticsearch.license.plugin.action.Utils.*; + +/** + * Contains metadata about registered snapshot licenses + */ +public class LicensesMetaData implements MetaData.Custom, ESLicenses { + + public static final String TYPE = "licenses"; + + public static final Factory FACTORY = new Factory(); + + private final ImmutableMap licenses; + + /** + * Constructs new repository metadata + * + * @param esLicenses list of esLicense + */ + public LicensesMetaData(List esLicenses) { + this.licenses = map(esLicenses); + } + + public LicensesMetaData(ESLicenses esLicenses) { + this.licenses = map(esLicenses); + } + + private static ImmutableMap map(Iterable esLicenses) { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ESLicense esLicense : esLicenses) { + builder.put(esLicense.feature(), esLicense); + } + return builder.build(); + } + + /** + * Returns list of currently registered licenses + * + * @return list of licenses + */ + //public ImmutableList signatures() { + // return this.licenses; + //} + + @Override + public Collection licenses() { + return licenses.values(); + } + + @Override + public Set features() { + return licenses.keySet(); + } + + @Override + public ESLicense get(FeatureType featureType) { + return licenses.get(featureType); + } + + @Override + public Iterator iterator() { + return licenses.values().iterator(); + } + + /** + * Repository metadata factory + */ + public static class Factory implements MetaData.Custom.Factory { + + /** + * {@inheritDoc} + */ + @Override + public String type() { + return TYPE; + } + + /** + * {@inheritDoc} + */ + @Override + public LicensesMetaData readFrom(StreamInput in) throws IOException { + return new LicensesMetaData(readLicensesFrom(in)); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeTo(LicensesMetaData licensesMetaData, StreamOutput out) throws IOException { + writeLicensesTo(licensesMetaData, out); + } + + /** + * {@inheritDoc} + */ + @Override + public LicensesMetaData fromXContent(XContentParser parser) throws IOException { + + XContentParser.Token token; + List licenses = null; + String fieldName = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + fieldName = parser.currentName(); + } + if (fieldName != null && fieldName.equals(Fields.LICENSES)) { + if (licenses == null) { + licenses = new ArrayList<>(); + } + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + licenses.add(licenseFromMap(parser.map())); + } + } + } + if (licenses == null) { + throw new ElasticsearchParseException("failed to parse licenses: expected ['" + Fields.LICENSES + "']"); + } + return new LicensesMetaData(licenses); + } + + /** + * {@inheritDoc} + */ + @Override + public void toXContent(LicensesMetaData licensesMetaData, XContentBuilder builder, ToXContent.Params params) throws IOException { + builder.startObject(); + builder.startArray(Fields.LICENSES); + for (ESLicense license : licensesMetaData.licenses()) { + builder.map(licenseAsMap(license)); + } + builder.endArray(); + builder.endObject(); + } + + @Override + public boolean isPersistent() { + return true; + } + + /* + @Override + public EnumSet context() { + return MetaData.API_AND_GATEWAY; + }*/ + + + private final static class Fields { + private static final String LICENSES = "licenses"; + } + + + } +} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java b/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java index c190535cfc8..52eb0567f25 100644 --- a/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java +++ b/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java @@ -12,9 +12,16 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.license.core.ESLicenses; +import org.elasticsearch.license.plugin.action.get.GetLicenseAction; +import org.elasticsearch.license.plugin.action.get.GetLicenseRequest; +import org.elasticsearch.license.plugin.action.get.GetLicenseResponse; import org.elasticsearch.rest.*; +import org.elasticsearch.rest.action.support.AcknowledgedRestListener; +import org.elasticsearch.rest.action.support.RestBuilderListener; import static org.elasticsearch.client.Requests.getRepositoryRequest; +import static org.elasticsearch.license.plugin.action.Utils.licenseAsMap; import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestStatus.OK; @@ -28,23 +35,20 @@ public class RestGetLicenseAction extends BaseRestHandler { @Override public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) { - final String[] repositories = request.paramAsStringArray("repository", Strings.EMPTY_ARRAY); - //TODO: implement after custom metadata impl - /* - GetRepositoriesRequest getRepositoriesRequest = getRepositoryRequest(repositories); - getRepositoriesRequest.masterNodeTimeout(request.paramAsTime("master_timeout", getRepositoriesRequest.masterNodeTimeout())); - getRepositoriesRequest.local(request.paramAsBoolean("local", getRepositoriesRequest.local())); - client.admin().cluster().getRepositories(getRepositoriesRequest, new RestBuilderListener(channel) { - @Override - public RestResponse buildResponse(GetRepositoriesResponse response, XContentBuilder builder) throws Exception { - builder.startObject(); - for (RepositoryMetaData repositoryMetaData : response.repositories()) { - RepositoriesMetaData.FACTORY.toXContent(repositoryMetaData, builder, request); - } - builder.endObject(); + GetLicenseRequest getLicenseRequest = new GetLicenseRequest(); + client.admin().cluster().execute(GetLicenseAction.INSTANCE, getLicenseRequest, new RestBuilderListener(channel) { + @Override + public RestResponse buildResponse(GetLicenseResponse getLicenseResponse, XContentBuilder builder) throws Exception { + builder.startObject(); + builder.startArray("licenses"); + for (ESLicenses.ESLicense license : getLicenseResponse.licenses()) { + builder.map(licenseAsMap(license)); + } + builder.endArray(); + builder.endObject(); return new BytesRestResponse(OK, builder); } - });*/ + }); } } diff --git a/src/main/java/org/elasticsearch/license/plugin/rest/RestPutLicenseAction.java b/src/main/java/org/elasticsearch/license/plugin/rest/RestPutLicenseAction.java index 612cd1798b1..0acbc9503db 100644 --- a/src/main/java/org/elasticsearch/license/plugin/rest/RestPutLicenseAction.java +++ b/src/main/java/org/elasticsearch/license/plugin/rest/RestPutLicenseAction.java @@ -35,7 +35,6 @@ public class RestPutLicenseAction extends BaseRestHandler { PutLicenseRequest putLicenseRequest = new PutLicenseRequest(); putLicenseRequest.listenerThreaded(false); putLicenseRequest.license(request.content().toUtf8()); - //TODO hookup new action client.admin().cluster().execute(PutLicenseAction.INSTANCE, putLicenseRequest, new AcknowledgedRestListener(channel)); } } diff --git a/src/main/java/org/elasticsearch/license/plugin/service/LicensesService.java b/src/main/java/org/elasticsearch/license/plugin/service/LicensesService.java new file mode 100644 index 00000000000..90fc21a7c9d --- /dev/null +++ b/src/main/java/org/elasticsearch/license/plugin/service/LicensesService.java @@ -0,0 +1,450 @@ +/* + * 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.service; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.cluster.*; +import org.elasticsearch.cluster.ack.ClusterStateUpdateRequest; +import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.component.AbstractComponent; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.inject.Injector; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.core.ESLicenses; +import org.elasticsearch.license.core.LicenseBuilders; +import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; +import org.elasticsearch.license.plugin.cluster.LicensesMetaData; + +/** + * Service responsible for maintaining and providing access to snapshot repositories on nodes. + */ +public class LicensesService extends AbstractComponent implements ClusterStateListener { + + // private final RepositoryTypesRegistry typesRegistry; + + private final Injector injector; + + private final ClusterService clusterService; + + //private volatile ESLicenses licenses = null;//ImmutableMap.of(); + + @Inject + public LicensesService(Settings settings, ClusterService clusterService, Injector injector) { + super(settings); + this.injector = injector; + this.clusterService = clusterService; + // Doesn't make sense to maintain repositories on non-master and non-data nodes + // Nothing happens there anyway + if (DiscoveryNode.dataNode(settings) || DiscoveryNode.masterNode(settings)) { + clusterService.add(this); + } + } + + /** + * Registers new repository in the cluster + *

+ * This method can be only called on the master node. It tries to create a new repository on the master + * and if it was successful it adds new repository to cluster metadata. + * + * @param request register repository request + * @param listener register repository listener + */ + public void registerLicenses(final String source, final PutLicenseRequest request, final ActionListener listener) { + final LicensesMetaData newLicenseMetaData = new LicensesMetaData(request.license()); + //TODO: add a source field to request + clusterService.submitStateUpdateTask(source, new AckedClusterStateUpdateTask(request, listener) { + @Override + protected ClusterStateUpdateResponse newResponse(boolean acknowledged) { + return new ClusterStateUpdateResponse(acknowledged); + } + + @Override + public ClusterState execute(ClusterState currentState) throws Exception { + // TODO check if newLicenseMetaData actually needs a cluster update + MetaData metaData = currentState.metaData(); + MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); + LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE); + + if (currentLicenses == null) { + // no licenses were registered + currentLicenses = newLicenseMetaData; + } else { + // merge previous license with new one + currentLicenses = new LicensesMetaData(LicenseBuilders.merge(currentLicenses, newLicenseMetaData)); + } + mdBuilder.putCustom(LicensesMetaData.TYPE, currentLicenses); + return ClusterState.builder(currentState).metaData(mdBuilder).build(); + } + }); + + /* + final RepositoryMetaData newRepositoryMetaData = new RepositoryMetaData(request.name, request.type, request.settings); + + clusterService.submitStateUpdateTask(request.cause, new AckedClusterStateUpdateTask(request, listener) { + @Override + protected ClusterStateUpdateResponse newResponse(boolean acknowledged) { + return new ClusterStateUpdateResponse(acknowledged); + } + + @Override + public ClusterState execute(ClusterState currentState) { + ensureRepositoryNotInUse(currentState, request.name); + // Trying to create the new repository on master to make sure it works + if (!registerLicenses(newRepositoryMetaData)) { + // The new repository has the same settings as the old one - ignore + return currentState; + } + MetaData metaData = currentState.metaData(); + MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); + RepositoriesMetaData repositories = metaData.custom(RepositoriesMetaData.TYPE); + if (repositories == null) { + logger.info("put repository [{}]", request.name); + repositories = new RepositoriesMetaData(new RepositoryMetaData(request.name, request.type, request.settings)); + } else { + boolean found = false; + List repositoriesMetaData = new ArrayList<>(repositories.repositories().size() + 1); + + for (RepositoryMetaData repositoryMetaData : repositories.repositories()) { + if (repositoryMetaData.name().equals(newRepositoryMetaData.name())) { + found = true; + repositoriesMetaData.add(newRepositoryMetaData); + } else { + repositoriesMetaData.add(repositoryMetaData); + } + } + if (!found) { + logger.info("put repository [{}]", request.name); + repositoriesMetaData.add(new RepositoryMetaData(request.name, request.type, request.settings)); + } else { + logger.info("update repository [{}]", request.name); + } + repositories = new RepositoriesMetaData(repositoriesMetaData.toArray(new RepositoryMetaData[repositoriesMetaData.size()])); + } + mdBuilder.putCustom(RepositoriesMetaData.TYPE, repositories); + return ClusterState.builder(currentState).metaData(mdBuilder).build(); + } + + @Override + public void onFailure(String source, Throwable t) { + logger.warn("failed to create repository [{}]", t, request.name); + super.onFailure(source, t); + } + + @Override + public boolean mustAck(DiscoveryNode discoveryNode) { + return discoveryNode.masterNode(); + } + });*/ + } + + /** + * Unregisters repository in the cluster + *

+ * This method can be only called on the master node. It removes repository information from cluster metadata. + * + * @param request unregister repository request + * @param listener unregister repository listener + */ + public void unregisterRepository(final UnregisterRepositoryRequest request, final ActionListener listener) { + /* + clusterService.submitStateUpdateTask(request.cause, new AckedClusterStateUpdateTask(request, listener) { + @Override + protected ClusterStateUpdateResponse newResponse(boolean acknowledged) { + return new ClusterStateUpdateResponse(acknowledged); + } + + @Override + public ClusterState execute(ClusterState currentState) { + ensureRepositoryNotInUse(currentState, request.name); + MetaData metaData = currentState.metaData(); + MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); + RepositoriesMetaData repositories = metaData.custom(RepositoriesMetaData.TYPE); + if (repositories != null && repositories.repositories().size() > 0) { + List repositoriesMetaData = new ArrayList<>(repositories.repositories().size()); + boolean changed = false; + for (RepositoryMetaData repositoryMetaData : repositories.repositories()) { + if (Regex.simpleMatch(request.name, repositoryMetaData.name())) { + logger.info("delete repository [{}]", repositoryMetaData.name()); + changed = true; + } else { + repositoriesMetaData.add(repositoryMetaData); + } + } + if (changed) { + repositories = new RepositoriesMetaData(repositoriesMetaData.toArray(new RepositoryMetaData[repositoriesMetaData.size()])); + mdBuilder.putCustom(RepositoriesMetaData.TYPE, repositories); + return ClusterState.builder(currentState).metaData(mdBuilder).build(); + } + } + throw new RepositoryMissingException(request.name); + } + + @Override + public boolean mustAck(DiscoveryNode discoveryNode) { + // Since operation occurs only on masters, it's enough that only master-eligible nodes acked + return discoveryNode.masterNode(); + } + });*/ + } + + /** + * Checks if new repositories appeared in or disappeared from cluster metadata and updates current list of + * repositories accordingly. + * + * @param event cluster changed event + */ + @Override + public void clusterChanged(ClusterChangedEvent event) { + /* + try { + RepositoriesMetaData oldMetaData = event.previousState().getMetaData().custom(RepositoriesMetaData.TYPE); + RepositoriesMetaData newMetaData = event.state().getMetaData().custom(RepositoriesMetaData.TYPE); + + // Check if repositories got changed + if ((oldMetaData == null && newMetaData == null) || (oldMetaData != null && oldMetaData.equals(newMetaData))) { + return; + } + + logger.trace("processing new index repositories for state version [{}]", event.state().version()); + + Map survivors = newHashMap(); + // First, remove repositories that are no longer there + for (Map.Entry entry : repositories.entrySet()) { + if (newMetaData == null || newMetaData.repository(entry.getKey()) == null) { + logger.debug("unregistering repository [{}]", entry.getKey()); + closeRepository(entry.getKey(), entry.getValue()); + } else { + survivors.put(entry.getKey(), entry.getValue()); + } + } + + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (newMetaData != null) { + // Now go through all repositories and update existing or create missing + for (RepositoryMetaData repositoryMetaData : newMetaData.repositories()) { + RepositoryHolder holder = survivors.get(repositoryMetaData.name()); + if (holder != null) { + // Found previous version of this repository + if (!holder.type.equals(repositoryMetaData.type()) || !holder.settings.equals(repositoryMetaData.settings())) { + // Previous version is different from the version in settings + logger.debug("updating repository [{}]", repositoryMetaData.name()); + closeRepository(repositoryMetaData.name(), holder); + holder = createRepositoryHolder(repositoryMetaData); + } + } else { + holder = createRepositoryHolder(repositoryMetaData); + } + if (holder != null) { + logger.debug("registering repository [{}]", repositoryMetaData.name()); + builder.put(repositoryMetaData.name(), holder); + } + } + } + repositories = builder.build(); + } catch (Throwable ex) { + logger.warn("failure updating cluster state ", ex); + }*/ + } + + + /** + * Returns registered repository + *

+ * This method is called only on the master node + * + * @param repository repository name + * @return registered repository + * @throws RepositoryMissingException if repository with such name isn't registered + */ + /* + public Repository repository(String repository) { + RepositoryHolder holder = repositories.get(repository); + if (holder != null) { + return holder.repository; + } + throw new RepositoryMissingException(repository); + }*/ + + /** + * Returns registered index shard repository + *

+ * This method is called only on data nodes + * + * @param repository repository name + * @return registered repository + * @throws RepositoryMissingException if repository with such name isn't registered + */ + /* + public IndexShardRepository indexShardRepository(String repository) { + RepositoryHolder holder = repositories.get(repository); + if (holder != null) { + return holder.indexShardRepository; + } + throw new RepositoryMissingException(repository); + }*/ + + /** + * Creates a new repository and adds it to the list of registered repositories. + *

+ * If a repository with the same name but different types or settings already exists, it will be closed and + * replaced with the new repository. If a repository with the same name exists but it has the same type and settings + * the new repository is ignored. + * + * @param repositoryMetaData new repository metadata + * @return {@code true} if new repository was added or {@code false} if it was ignored + */ + /* + private boolean registerLicenses(RepositoryMetaData repositoryMetaData) { + RepositoryHolder previous = repositories.get(repositoryMetaData.name()); + if (previous != null) { + if (!previous.type.equals(repositoryMetaData.type()) && previous.settings.equals(repositoryMetaData.settings())) { + // Previous version is the same as this one - ignore it + return false; + } + } + RepositoryHolder holder = createRepositoryHolder(repositoryMetaData); + if (previous != null) { + // Closing previous version + closeRepository(repositoryMetaData.name(), previous); + } + Map newRepositories = newHashMap(repositories); + newRepositories.put(repositoryMetaData.name(), holder); + repositories = ImmutableMap.copyOf(newRepositories); + return true; + } +*/ + /** + * Closes the repository + * + * @param name repository name + * @param holder repository holder + */ + /* + private void closeRepository(String name, RepositoryHolder holder) { + logger.debug("closing repository [{}][{}]", holder.type, name); + if (holder.injector != null) { + Injectors.close(holder.injector); + } + if (holder.repository != null) { + holder.repository.close(); + } + }*/ + + /** + * Creates repository holder + */ + /* + private RepositoryHolder createRepositoryHolder(RepositoryMetaData repositoryMetaData) { + logger.debug("creating repository [{}][{}]", repositoryMetaData.type(), repositoryMetaData.name()); + Injector repositoryInjector = null; + try { + ModulesBuilder modules = new ModulesBuilder(); + RepositoryName name = new RepositoryName(repositoryMetaData.type(), repositoryMetaData.name()); + modules.add(new RepositoryNameModule(name)); + modules.add(new RepositoryModule(name, repositoryMetaData.settings(), this.settings, typesRegistry)); + + repositoryInjector = modules.createChildInjector(injector); + Repository repository = repositoryInjector.getInstance(Repository.class); + IndexShardRepository indexShardRepository = repositoryInjector.getInstance(IndexShardRepository.class); + repository.start(); + return new RepositoryHolder(repositoryMetaData.type(), repositoryMetaData.settings(), repositoryInjector, repository, indexShardRepository); + } catch (Throwable t) { + if (repositoryInjector != null) { + Injectors.close(repositoryInjector); + } + logger.warn("failed to create repository [{}][{}]", t, repositoryMetaData.type(), repositoryMetaData.name()); + throw new RepositoryException(repositoryMetaData.name(), "failed to create repository", t); + } + } + + private void ensureRepositoryNotInUse(ClusterState clusterState, String repository) { + if (SnapshotsService.isRepositoryInUse(clusterState, repository) || RestoreService.isRepositoryInUse(clusterState, repository)) { + throw new ElasticsearchIllegalStateException("trying to modify or unregister repository that is currently used "); + } + }*/ + + /** + * Internal data structure for holding repository with its configuration information and injector + */ + /* private static class RepositoryHolder { + + private final String type; + private final Settings settings; + private final Injector injector; + private final Repository repository; + private final IndexShardRepository indexShardRepository; + + public RepositoryHolder(String type, Settings settings, Injector injector, Repository repository, IndexShardRepository indexShardRepository) { + this.type = type; + this.settings = settings; + this.repository = repository; + this.indexShardRepository = indexShardRepository; + this.injector = injector; + } + } +*/ + /** + * Register repository request + */ + public static class RegisterRepositoryRequest extends ClusterStateUpdateRequest { + + final String cause; + + final String name; + + final String type; + + Settings settings = null; + + /** + * Constructs new register repository request + * + * @param cause repository registration cause + * @param name repository name + * @param type repository type + */ + public RegisterRepositoryRequest(String cause, String name, String type) { + this.cause = cause; + this.name = name; + this.type = type; + } + + /** + * Sets repository settings + * + * @param settings repository settings + * @return this request + */ + public RegisterRepositoryRequest settings(Settings settings) { + this.settings = settings; + return this; + } + } + + /** + * Unregister repository request + */ + public static class UnregisterRepositoryRequest extends ClusterStateUpdateRequest { + + final String cause; + + final String name; + + /** + * Creates a new unregister repository request + * + * @param cause repository unregistration cause + * @param name repository name + */ + public UnregisterRepositoryRequest(String cause, String name) { + this.cause = cause; + this.name = name; + } + + } +} diff --git a/src/test/java/org/elasticsearch/license/plugin/LicenseTransportTests.java b/src/test/java/org/elasticsearch/license/plugin/LicenseTransportTests.java new file mode 100644 index 00000000000..c9382dbec79 --- /dev/null +++ b/src/test/java/org/elasticsearch/license/plugin/LicenseTransportTests.java @@ -0,0 +1,85 @@ +/* + * 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.action.ActionFuture; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.license.TestUtils; +import org.elasticsearch.license.core.ESLicenses; +import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool; +import org.elasticsearch.license.plugin.action.put.PutLicenseAction; +import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; +import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; +import org.elasticsearch.license.plugin.cluster.LicensesMetaData; +import org.elasticsearch.test.ElasticsearchIntegrationTest; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +public class LicenseTransportTests extends ElasticsearchIntegrationTest { + + static { + MetaData.registerFactory(LicensesMetaData.TYPE, LicensesMetaData.FACTORY); + } + + private static String pubKeyPath = null; + private static String priKeyPath = null; + private static String keyPass = null; + + @BeforeClass + public static void setup() throws IOException { + + // Generate temp KeyPair spec + File privateKeyFile = File.createTempFile("privateKey", ".key"); + File publicKeyFile = File.createTempFile("publicKey", ".key"); + LicenseTransportTests.pubKeyPath = publicKeyFile.getAbsolutePath(); + LicenseTransportTests.priKeyPath = privateKeyFile.getAbsolutePath(); + assert privateKeyFile.delete(); + assert publicKeyFile.delete(); + String keyPass = "password"; + LicenseTransportTests.keyPass = keyPass; + + // Generate keyPair + String[] args = new String[6]; + args[0] = "--publicKeyPath"; + args[1] = LicenseTransportTests.pubKeyPath; + args[2] = "--privateKeyPath"; + args[3] = LicenseTransportTests.priKeyPath; + args[4] = "--keyPass"; + args[5] = LicenseTransportTests.keyPass; + KeyPairGeneratorTool.main(args); + } + + @Test + public void testPutLicense() throws ParseException, ExecutionException, InterruptedException { + + Map map = new HashMap<>(); + TestUtils.FeatureAttributes featureAttributes = + new TestUtils.FeatureAttributes("shield", "subscription", "platinum", "foo bar Inc.", "elasticsearch", 2, "2014-12-13", "2015-12-13"); + map.put(ESLicenses.FeatureType.SHIELD, featureAttributes); + + String licenseString = TestUtils.generateESLicenses(map); + + PutLicenseRequest putLicenseRequest = new PutLicenseRequest(); + putLicenseRequest.license(licenseString); + + final ActionFuture execute = client().admin().cluster().execute(PutLicenseAction.INSTANCE, putLicenseRequest); + + final PutLicenseResponse putLicenseResponse = execute.get(); + + assertThat(putLicenseResponse.isAcknowledged(), equalTo(true)); + + } +}