Repository: tinkerpop Updated Branches: refs/heads/tp32 88d6f7786 -> b510613b1
TINKERPOP-2044 Configurable traversal to validate host connectivity. Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/b510613b Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/b510613b Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/b510613b Branch: refs/heads/tp32 Commit: b510613b10962a3e8d95c3e590b5f58297227d0e Parents: 88d6f77 Author: Stephen Mallette <sp...@genoprime.com> Authored: Fri Sep 28 15:54:30 2018 -0400 Committer: Stephen Mallette <sp...@genoprime.com> Committed: Thu Oct 4 08:16:44 2018 -0400 ---------------------------------------------------------------------- CHANGELOG.asciidoc | 1 + .../src/reference/gremlin-applications.asciidoc | 1 + .../tinkerpop/gremlin/driver/Cluster.java | 30 ++++++++++++++++++-- .../gremlin/driver/ConnectionPool.java | 3 +- .../tinkerpop/gremlin/driver/Settings.java | 15 +++++++--- .../tinkerpop/gremlin/driver/SettingsTest.java | 8 ++++-- .../server/GremlinDriverIntegrateTest.java | 27 +++++++++++++++++- 7 files changed, 74 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b510613b/CHANGELOG.asciidoc ---------------------------------------------------------------------- diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index c4523d8..f3340cc 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -39,6 +39,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima * Added synchronized `Map` to Gryo 1.0 registrations. * Added `Triple` to Gryo 1.0 registrations. * Improved escaping of special characters in strings passed to the `GroovyTranslator`. +* Added `Cluster` configuration option to set a custom validation script to use to test server connectivity in the Java driver. * Improved ability of `GroovyTranslator` to handle more types supported by GraphSON. * Improved ability of `GroovyTranslator` to handle custom types. * Added better internal processing of `Column` in `by(Function)`. http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b510613b/docs/src/reference/gremlin-applications.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc index 1cd9964..5607667 100644 --- a/docs/src/reference/gremlin-applications.asciidoc +++ b/docs/src/reference/gremlin-applications.asciidoc @@ -750,6 +750,7 @@ The following table describes the various configuration options for the Gremlin |connectionPool.sslSkipCertValidation |Configures the `TrustManager` to trust all certs without any validation. Should not be used in production.|false |connectionPool.trustStore |File location for a SSL Certificate Chain to use when SSL is enabled. If this value is not provided and SSL is enabled, the default `TrustManager` will be used. |_none_ |connectionPool.trustStorePassword |The password of the `trustStore` if it is password-protected |_none_ +|connectionPool.validationRequest |A script that is used to test server connectivity. A good script to use is one that evaluates quickly and returns no data. The default simply returns an empty string, but if a graph is required by a particular provider, a good traversal might be `g.inject()`. |_''_ |hosts |The list of hosts that the driver will connect to. |localhost |jaasEntry |Sets the `AuthProperties.Property.JAAS_ENTRY` properties for authentication to Gremlin Server. |_none_ |nioPoolSize |Size of the pool for handling request/response operations. |available processors http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b510613b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java ---------------------------------------------------------------------- diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java index 9adaaa1..c090584 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java @@ -25,11 +25,16 @@ import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.apache.commons.configuration.Configuration; +import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; import org.apache.tinkerpop.gremlin.driver.ser.Serializers; import io.netty.bootstrap.Bootstrap; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion; +import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,10 +65,10 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -204,7 +209,8 @@ public final class Cluster { .maxSimultaneousUsagePerConnection(settings.connectionPool.maxSimultaneousUsagePerConnection) .minSimultaneousUsagePerConnection(settings.connectionPool.minSimultaneousUsagePerConnection) .maxConnectionPoolSize(settings.connectionPool.maxSize) - .minConnectionPoolSize(settings.connectionPool.minSize); + .minConnectionPoolSize(settings.connectionPool.minSize) + .validationRequest(settings.connectionPool.validationRequest); if (settings.username != null && settings.password != null) builder.credentials(settings.username, settings.password); @@ -465,6 +471,10 @@ public final class Cluster { return manager.authProps; } + RequestMessage.Builder validationRequest() { + return manager.validationRequest.get(); + } + SslContext createSSLContext() throws Exception { // if the context is provided then just use that and ignore the other settings if (manager.sslContextOptional.isPresent()) @@ -575,6 +585,7 @@ public final class Cluster { private String trustStore = null; private String trustStorePassword = null; private String keyStoreType = null; + private String validationRequest = "''"; private List<String> sslEnabledProtocols = new ArrayList<>(); private List<String> sslCipherSuites = new ArrayList<>(); private boolean sslSkipCertValidation = false; @@ -889,6 +900,17 @@ public final class Cluster { } /** + * Specify a valid Gremlin script that can be used to test remote operations. This script should be designed + * to return quickly with the least amount of overhead possible. By default, the script sends an empty string. + * If the graph does not support that sort of script because it requires all scripts to include a reference + * to a graph then a good option might be {@code g.inject()}. + */ + public Builder validationRequest(final String script) { + validationRequest = script; + return this; + } + + /** * Time in milliseconds to wait before attempting to reconnect to a dead host after it has been marked dead. * * @deprecated As of release 3.2.3, the value of the initial delay is now the same as the {@link #reconnectInterval}. @@ -1020,6 +1042,7 @@ public final class Cluster { private final LoadBalancingStrategy loadBalancingStrategy; private final AuthProperties authProps; private final Optional<SslContext> sslContextOptional; + private final Supplier<RequestMessage.Builder> validationRequest; private final ScheduledThreadPoolExecutor executor; @@ -1066,6 +1089,7 @@ public final class Cluster { connectionPoolSettings.sslSkipCertValidation = builder.sslSkipCertValidation; connectionPoolSettings.keepAliveInterval = builder.keepAliveInterval; connectionPoolSettings.channelizer = builder.channelizer; + connectionPoolSettings.validationRequest = builder.validationRequest; sslContextOptional = Optional.ofNullable(builder.sslContext); @@ -1079,6 +1103,8 @@ public final class Cluster { this.executor = new ScheduledThreadPoolExecutor(builder.workerPoolSize, new BasicThreadFactory.Builder().namingPattern("gremlin-driver-worker-%d").build()); this.executor.setRemoveOnCancelPolicy(true); + + validationRequest = () -> RequestMessage.build(Tokens.OPS_EVAL).add(Tokens.ARGS_GREMLIN, builder.validationRequest); } private void validateBuilder(final Builder builder) { http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b510613b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ConnectionPool.java ---------------------------------------------------------------------- diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ConnectionPool.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ConnectionPool.java index 8d63e13..39ded33 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ConnectionPool.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ConnectionPool.java @@ -387,7 +387,6 @@ final class ConnectionPool { // let the load-balancer know that the host is acting poorly this.cluster.loadBalancingStrategy().onUnavailable(host); - } /** @@ -400,7 +399,7 @@ final class ConnectionPool { Connection connection = null; try { connection = borrowConnection(cluster.connectionPoolSettings().maxWaitForConnection, TimeUnit.MILLISECONDS); - final RequestMessage ping = RequestMessage.build(Tokens.OPS_EVAL).add(Tokens.ARGS_GREMLIN, "''").create(); + final RequestMessage ping = client.buildMessage(cluster.validationRequest()).create(); final CompletableFuture<ResultSet> f = new CompletableFuture<>(); connection.write(ping, f); f.get().all().get(); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b510613b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java ---------------------------------------------------------------------- diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java index fedd337..c2ae045 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Settings.java @@ -243,6 +243,8 @@ final class Settings { if (connectionPoolConf.containsKey("keepAliveInterval")) cpSettings.keepAliveInterval = connectionPoolConf.getLong("keepAliveInterval"); + if (connectionPoolConf.containsKey("validationRequest")) + cpSettings.validationRequest = connectionPoolConf.getString("validationRequest"); settings.connectionPool = cpSettings; } @@ -258,28 +260,28 @@ final class Settings { /** * The trusted certificate in PEM format. - * @deprecated As of release 3.2.10, replaced by {@link trustStore} + * @deprecated As of release 3.2.10, replaced by {@link #trustStore} */ @Deprecated public String trustCertChainFile = null; /** * The X.509 certificate chain file in PEM format. - * @deprecated As of release 3.2.10, replaced by {@link keyStore} + * @deprecated As of release 3.2.10, replaced by {@link #keyStore} */ @Deprecated public String keyCertChainFile = null; /** * The PKCS#8 private key file in PEM format. - * @deprecated As of release 3.2.10, replaced by {@link keyStore} + * @deprecated As of release 3.2.10, replaced by {@link #keyStore} */ @Deprecated public String keyFile = null; /** * The password of the {@link #keyFile}, or {@code null} if it's not password-protected. - * @deprecated As of release 3.2.10, replaced by {@link keyStorePassword} + * @deprecated As of release 3.2.10, replaced by {@link #keyStorePassword} */ @Deprecated public String keyPassword = null; @@ -420,6 +422,11 @@ final class Settings { public String channelizer = Channelizer.WebSocketChannelizer.class.getName(); /** + * A valid Gremlin script that can be used to test remote operations. + */ + public String validationRequest = "''"; + + /** * @deprecated as of 3.1.1-incubating, and not replaced as this property was never implemented internally * as the way to establish sessions */ http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b510613b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java ---------------------------------------------------------------------- diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java index 56e0ec8..63c4308 100644 --- a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java +++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SettingsTest.java @@ -18,7 +18,9 @@ */ package org.apache.tinkerpop.gremlin.driver; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.Configuration; @@ -69,6 +71,7 @@ public class SettingsTest { conf.setProperty("connectionPool.reconnectInitialDelay", 1000); conf.setProperty("connectionPool.resultIterationBatchSize", 1100); conf.setProperty("connectionPool.channelizer", "channelizer0"); + conf.setProperty("connectionPool.validationRequest", "g.inject()"); final Settings settings = Settings.from(conf); @@ -82,7 +85,7 @@ public class SettingsTest { assertEquals(Arrays.asList("255.0.0.1", "255.0.0.2", "255.0.0.3"), settings.hosts); assertEquals("my.serializers.MySerializer", settings.serializer.className); assertEquals("thing", settings.serializer.config.get("any")); - assertEquals(true, settings.connectionPool.enableSsl); + assertThat(settings.connectionPool.enableSsl, is(true)); assertEquals("X.509", settings.connectionPool.keyCertChainFile); assertEquals("PKCS#8", settings.connectionPool.keyFile); assertEquals("password1", settings.connectionPool.keyPassword); @@ -94,7 +97,7 @@ public class SettingsTest { assertEquals("password3", settings.connectionPool.trustStorePassword); assertEquals(Arrays.asList("TLSv1.1","TLSv1.2"), settings.connectionPool.sslEnabledProtocols); assertEquals(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"), settings.connectionPool.sslCipherSuites); - assertEquals(true, settings.connectionPool.sslSkipCertValidation); + assertThat(settings.connectionPool.sslSkipCertValidation, is(true)); assertEquals(100, settings.connectionPool.minSize); assertEquals(200, settings.connectionPool.maxSize); assertEquals(300, settings.connectionPool.minSimultaneousUsagePerConnection); @@ -107,5 +110,6 @@ public class SettingsTest { assertEquals(1000, settings.connectionPool.reconnectInitialDelay); assertEquals(1100, settings.connectionPool.resultIterationBatchSize); assertEquals("channelizer0", settings.connectionPool.channelizer); + assertEquals("g.inject()", settings.connectionPool.validationRequest); } } http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b510613b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java ---------------------------------------------------------------------- diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java index c5ff608..1f759d0 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java @@ -285,7 +285,7 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration } @Test - public void shouldEventuallySucceedOnSameServer() throws Exception { + public void shouldEventuallySucceedOnSameServerWithDefault() throws Exception { stopServer(); final Cluster cluster = TestClientFactory.open(); @@ -310,6 +310,31 @@ public class GremlinDriverIntegrateTest extends AbstractGremlinServerIntegration } @Test + public void shouldEventuallySucceedOnSameServerWithScript() throws Exception { + stopServer(); + + final Cluster cluster = TestClientFactory.build().validationRequest("g.inject()").create(); + final Client client = cluster.connect(); + + try { + client.submit("1+1").all().join().get(0).getInt(); + fail("Should not have gone through because the server is not running"); + } catch (Exception i) { + final Throwable root = ExceptionUtils.getRootCause(i); + assertThat(root, instanceOf(TimeoutException.class)); + } + + startServer(); + + // default reconnect time is 1 second so wait some extra time to be sure it has time to try to bring it + // back to life + TimeUnit.SECONDS.sleep(3); + assertEquals(2, client.submit("1+1").all().join().get(0).getInt()); + + cluster.close(); + } + + @Test public void shouldEventuallySucceedWithRoundRobin() throws Exception { final String noGremlinServer = "74.125.225.19"; final Cluster cluster = TestClientFactory.build().addContactPoint(noGremlinServer).create();