diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/Licensing.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/Licensing.java index 4837f733728..22c95840dd9 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/Licensing.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/Licensing.java @@ -6,8 +6,6 @@ package org.elasticsearch.license.plugin; import org.elasticsearch.action.ActionModule; -import org.elasticsearch.client.Client; -import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.component.LifecycleComponent; import org.elasticsearch.common.inject.Inject; @@ -28,15 +26,19 @@ import org.elasticsearch.license.plugin.rest.RestDeleteLicenseAction; import org.elasticsearch.license.plugin.rest.RestGetLicenseAction; import org.elasticsearch.license.plugin.rest.RestPutLicenseAction; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import static org.elasticsearch.xpack.XPackPlugin.isTribeClientNode; +import static org.elasticsearch.xpack.XPackPlugin.isTribeNode; +import static org.elasticsearch.xpack.XPackPlugin.transportClientMode; + + public class Licensing { public static final String NAME = "license"; - private final boolean isEnabled; - protected final boolean transportClient; + private final boolean isTransportClient; + private final boolean isTribeNode; static { MetaData.registerPrototype(LicensesMetaData.TYPE, LicensesMetaData.PROTO); @@ -44,12 +46,12 @@ public class Licensing { @Inject public Licensing(Settings settings) { - this.transportClient = TransportClient.CLIENT_TYPE.equals(settings.get(Client.CLIENT_TYPE_SETTING_S.getKey())); - isEnabled = transportClient == false; + isTransportClient = transportClientMode(settings); + isTribeNode = isTribeNode(settings); } public void onModule(NetworkModule module) { - if (transportClient == false) { + if (isTransportClient == false && isTribeNode == false) { module.registerRestHandler(RestPutLicenseAction.class); module.registerRestHandler(RestGetLicenseAction.class); module.registerRestHandler(RestDeleteLicenseAction.class); @@ -57,21 +59,22 @@ public class Licensing { } public void onModule(ActionModule module) { - module.registerAction(PutLicenseAction.INSTANCE, TransportPutLicenseAction.class); - module.registerAction(GetLicenseAction.INSTANCE, TransportGetLicenseAction.class); - module.registerAction(DeleteLicenseAction.INSTANCE, TransportDeleteLicenseAction.class); + if (isTribeNode == false) { + module.registerAction(PutLicenseAction.INSTANCE, TransportPutLicenseAction.class); + module.registerAction(GetLicenseAction.INSTANCE, TransportGetLicenseAction.class); + module.registerAction(DeleteLicenseAction.INSTANCE, TransportDeleteLicenseAction.class); + } } public Collection> nodeServices() { - Collection> services = new ArrayList<>(); - if (isEnabled) { - services.add(LicensesService.class); + if (isTransportClient == false && isTribeNode == false) { + return Collections.>singletonList(LicensesService.class); } - return services; + return Collections.emptyList(); } public Collection nodeModules() { - if (isEnabled) { + if (isTransportClient == false && isTribeNode == false) { return Collections.singletonList(new LicensingModule()); } return Collections.emptyList(); diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicenseTribeTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicenseTribeTests.java new file mode 100644 index 00000000000..7a8fe75f639 --- /dev/null +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicenseTribeTests.java @@ -0,0 +1,234 @@ +/* + * 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.Action; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.Requests; +import org.elasticsearch.client.transport.NoNodeAvailableException; +import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.common.Priority; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.common.network.NetworkModule; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.discovery.zen.ping.unicast.UnicastZenPing; +import org.elasticsearch.graph.Graph; +import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; +import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest; +import org.elasticsearch.license.plugin.action.get.GetLicenseAction; +import org.elasticsearch.license.plugin.action.get.GetLicenseRequest; +import org.elasticsearch.license.plugin.action.put.PutLicenseAction; +import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; +import org.elasticsearch.marvel.Monitoring; +import org.elasticsearch.node.Node; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.shield.Security; +import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.ESIntegTestCase.ClusterScope; +import org.elasticsearch.test.ESIntegTestCase.Scope; +import org.elasticsearch.test.InternalTestCluster; +import org.elasticsearch.test.NodeConfigurationSource; +import org.elasticsearch.test.TestCluster; +import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.watcher.Watcher; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +@ClusterScope(scope = Scope.TEST, transportClientRatio = 0, numClientNodes = 0, numDataNodes = 0) +public class LicenseTribeTests extends ESIntegTestCase { + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return Settings.builder() + .put(XPackPlugin.featureEnabledSetting(Security.NAME), false) + .put(XPackPlugin.featureEnabledSetting(Monitoring.NAME), false) + .put(XPackPlugin.featureEnabledSetting(Watcher.NAME), false) + .put(XPackPlugin.featureEnabledSetting(Graph.NAME), false) + .put(NetworkModule.HTTP_ENABLED.getKey(), false) + .put(Node.NODE_LOCAL_SETTING.getKey(), true) + .build(); + } + + @Override + protected Collection> nodePlugins() { + return Collections.>singletonList(XPackPlugin.class); + } + + @Override + protected Collection> transportClientPlugins() { + return nodePlugins(); + } + + public void testTribeSetup() throws Exception { + NodeConfigurationSource nodeConfigurationSource = new NodeConfigurationSource() { + @Override + public Settings nodeSettings(int nodeOrdinal) { + return LicenseTribeTests.this.nodeSettings(nodeOrdinal); + } + + @Override + public Collection> nodePlugins() { + return LicenseTribeTests.this.nodePlugins(); + } + + @Override + public Settings transportClientSettings() { + return LicenseTribeTests.this.transportClientSettings(); + } + + @Override + public Collection> transportClientPlugins() { + return LicenseTribeTests.this.transportClientPlugins(); + } + }; + final InternalTestCluster cluster2 = new InternalTestCluster(InternalTestCluster.configuredNodeMode(), + randomLong(), createTempDir(), 2, 2, + UUIDs.randomBase64UUID(random()), nodeConfigurationSource, 0, false, "tribe_node2", + getMockPlugins(), getClientWrapper()); + + cluster2.beforeTest(random(), 0.0); + cluster2.ensureAtLeastNumDataNodes(2); + + logger.info("create 2 indices, test1 on t1, and test2 on t2"); + assertAcked(internalCluster().client().admin().indices().prepareCreate("test1").get()); + assertAcked(cluster2.client().admin().indices().prepareCreate("test2").get()); + ensureYellow(internalCluster()); + ensureYellow(cluster2); + Map asMap = internalCluster().getDefaultSettings().getAsMap(); + Settings.Builder tribe1Defaults = Settings.builder(); + Settings.Builder tribe2Defaults = Settings.builder(); + for (Map.Entry entry : asMap.entrySet()) { + if (entry.getKey().startsWith("path.")) { + continue; + } + tribe1Defaults.put("tribe.t1." + entry.getKey(), entry.getValue()); + tribe2Defaults.put("tribe.t2." + entry.getKey(), entry.getValue()); + } + // give each tribe it's unicast hosts to connect to + tribe1Defaults.putArray("tribe.t1." + UnicastZenPing.DISCOVERY_ZEN_PING_UNICAST_HOSTS_SETTING.getKey(), + getUnicastHosts(internalCluster().client())); + tribe1Defaults.putArray("tribe.t2." + UnicastZenPing.DISCOVERY_ZEN_PING_UNICAST_HOSTS_SETTING.getKey(), + getUnicastHosts(cluster2.client())); + + Settings merged = Settings.builder() + .put("tribe.t1.cluster.name", internalCluster().getClusterName()) + .put("tribe.t2.cluster.name", cluster2.getClusterName()) + .put("tribe.blocks.write", false) + .put(tribe1Defaults.build()) + .put(tribe2Defaults.build()) + .put(internalCluster().getDefaultSettings()) + .put("node.name", "tribe_node") // make sure we can identify threads from this node + .put(Node.NODE_LOCAL_SETTING.getKey(), true) + .build(); + + final Node tribeNode = new Node(merged).start(); + Client tribeClient = tribeNode.client(); + + logger.info("wait till tribe has the same nodes as the 2 clusters"); + assertBusy(() -> { + DiscoveryNodes tribeNodes = tribeNode.client().admin().cluster().prepareState().get().getState().getNodes(); + assertThat(countDataNodesForTribe("t1", tribeNodes), + equalTo(internalCluster().client().admin().cluster().prepareState().get().getState() + .getNodes().getDataNodes().size())); + assertThat(countDataNodesForTribe("t2", tribeNodes), + equalTo(cluster2.client().admin().cluster().prepareState().get().getState() + .getNodes().getDataNodes().size())); + }); + verifyActionOnTribeNode(tribeClient); + verifyActionOnDataNode((randomBoolean() ? internalCluster() : cluster2).dataNodeClient()); + verifyActionOnDataNode((randomBoolean() ? internalCluster() : cluster2).masterClient()); + try { + cluster2.wipe(Collections.emptySet()); + } catch (NoNodeAvailableException ignored) { + } finally { + cluster2.afterTest(); + } + tribeNode.close(); + cluster2.close(); + } + + protected void verifyActionOnDataNode(Client dataNodeClient) throws Exception { + dataNodeClient.execute(GetLicenseAction.INSTANCE, new GetLicenseRequest()).get(); + dataNodeClient.execute(PutLicenseAction.INSTANCE, new PutLicenseRequest() + .license(generateSignedLicense(TimeValue.timeValueHours(1)))); + dataNodeClient.execute(DeleteLicenseAction.INSTANCE, new DeleteLicenseRequest()); + } + + protected void verifyActionOnTribeNode(Client tribeClient) { + failAction(tribeClient, GetLicenseAction.INSTANCE); + failAction(tribeClient, PutLicenseAction.INSTANCE); + failAction(tribeClient, DeleteLicenseAction.INSTANCE); + } + + protected void failAction(Client client, Action action) { + try { + client.execute(action, action.newRequestBuilder(client).request()); + fail("expected [" + action.name() + "] to fail"); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), containsString("failed to find action")); + } + } + + private void ensureYellow(TestCluster testCluster) { + ClusterHealthResponse actionGet; + while (true) { + try { + actionGet = testCluster.client().admin().cluster() + .health(Requests.clusterHealthRequest().waitForYellowStatus() + .waitForEvents(Priority.LANGUID).waitForRelocatingShards(0)).actionGet(); + break; + } catch (NoNodeAvailableException ignored) { + } + } + if (actionGet.isTimedOut()) { + logger.info("ensureGreen timed out, cluster state:\n{}\n{}", testCluster.client().admin().cluster() + .prepareState().get().getState().prettyPrint(), + testCluster.client().admin().cluster().preparePendingClusterTasks().get().prettyPrint()); + assertThat("timed out waiting for yellow state", actionGet.isTimedOut(), equalTo(false)); + } + assertThat(actionGet.getStatus(), anyOf(equalTo(ClusterHealthStatus.YELLOW), equalTo(ClusterHealthStatus.GREEN))); + } + + private int countDataNodesForTribe(String tribeName, DiscoveryNodes nodes) { + int count = 0; + for (DiscoveryNode node : nodes) { + if (!node.isDataNode()) { + continue; + } + if (tribeName.equals(node.getAttributes().get("tribe.name"))) { + count++; + } + } + return count; + } + + public static String[] getUnicastHosts(Client client) { + ArrayList unicastHosts = new ArrayList<>(); + NodesInfoResponse nodeInfos = client.admin().cluster().prepareNodesInfo().clear().setTransport(true).get(); + for (NodeInfo info : nodeInfos.getNodes()) { + TransportAddress address = info.getTransport().getAddress().publishAddress(); + unicastHosts.add(address.getAddress() + ":" + address.getPort()); + } + return unicastHosts.toArray(new String[unicastHosts.size()]); + } + +} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java index 4a0f7343458..1e3db66f25e 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.license.plugin; import org.elasticsearch.action.ActionFuture; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.license.core.License; import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; @@ -18,20 +19,42 @@ import org.elasticsearch.license.plugin.action.put.PutLicenseAction; import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder; import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; import org.elasticsearch.license.plugin.core.LicensesStatus; -import org.elasticsearch.test.ESIntegTestCase.ClusterScope; -import org.junit.After; +import org.elasticsearch.marvel.Monitoring; +import org.elasticsearch.node.Node; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.shield.Security; +import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.watcher.Watcher; + +import java.util.Collection; +import java.util.Collections; import static org.elasticsearch.license.plugin.TestUtils.dateMath; import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense; -import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; -@ClusterScope(scope = TEST, numDataNodes = 10) -public class LicensesTransportTests extends AbstractLicensesIntegrationTestCase { - @After - public void beforeTest() throws Exception { - wipeAllLicenses(); +public class LicensesTransportTests extends ESSingleNodeTestCase { + + @Override + protected boolean resetNodeAfterTest() { + return true; + } + + @Override + protected Collection> getPlugins() { + return Collections.singletonList(XPackPlugin.class); + } + + @Override + protected Settings nodeSettings() { + Settings.Builder newSettings = Settings.builder(); + newSettings.put(XPackPlugin.featureEnabledSetting(Security.NAME), false); + newSettings.put(XPackPlugin.featureEnabledSetting(Monitoring.NAME), false); + newSettings.put(XPackPlugin.featureEnabledSetting(Watcher.NAME), false); + newSettings.put(Node.NODE_DATA_SETTING.getKey(), true); + return newSettings.build(); } public void testEmptyGetLicense() throws Exception { @@ -47,7 +70,8 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTestCase // put license PutLicenseRequestBuilder putLicenseRequestBuilder = - new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(signedLicense); + new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(signedLicense) + .setAcknowledge(true); PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get(); assertThat(putLicenseResponse.isAcknowledged(), equalTo(true)); assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID)); @@ -63,7 +87,8 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTestCase // put license source PutLicenseRequestBuilder putLicenseRequestBuilder = - new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(licenseString); + new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(licenseString) + .setAcknowledge(true); PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get(); assertThat(putLicenseResponse.isAcknowledged(), equalTo(true)); assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID)); @@ -97,25 +122,20 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTestCase public void testPutExpiredLicense() throws Exception { License expiredLicense = generateSignedLicense(dateMath("now-10d/d", System.currentTimeMillis()), TimeValue.timeValueMinutes(2)); - License signedLicense = generateSignedLicense(TimeValue.timeValueMinutes(2)); - PutLicenseRequestBuilder builder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE); - builder.setLicense(signedLicense); - // put license should return valid (as there is one valid license) - PutLicenseResponse putLicenseResponse = builder.get(); - assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID)); builder.setLicense(expiredLicense); - putLicenseResponse = builder.get(); + PutLicenseResponse putLicenseResponse = builder.get(); assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.EXPIRED)); // get license should not return the expired license GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get(); - assertThat(getLicenseResponse.license(), equalTo(signedLicense)); + assertThat(getLicenseResponse.license(), not(expiredLicense)); } public void testPutLicensesSimple() throws Exception { License basicSignedLicense = generateSignedLicense("basic", TimeValue.timeValueMinutes(5)); PutLicenseRequestBuilder putLicenseRequestBuilder = - new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(basicSignedLicense); + new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(basicSignedLicense) + .setAcknowledge(true); PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get(); assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID)); GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get(); @@ -133,7 +153,8 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTestCase public void testRemoveLicensesSimple() throws Exception { License goldLicense = generateSignedLicense("gold", TimeValue.timeValueMinutes(5)); PutLicenseRequestBuilder putLicenseRequestBuilder = - new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(goldLicense); + new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(goldLicense) + .setAcknowledge(true); PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get(); assertThat(putLicenseResponse.isAcknowledged(), equalTo(true)); assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));