Cole-Greer commented on code in PR #3328:
URL: https://github.com/apache/tinkerpop/pull/3328#discussion_r2967807755


##########
gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java:
##########
@@ -293,6 +293,11 @@ public synchronized CompletableFuture<Void> stop() {
                 logger.warn("Timeout waiting for boss/worker thread pools to 
shutdown - continuing with shutdown process.");
             }
 
+            if (serverGremlinExecutor != null) {

Review Comment:
   Nit: can this be folded into the if-statement below



##########
gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpTransactionIntegrateTest.java:
##########
@@ -0,0 +1,753 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.server;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import org.apache.http.Consts;
+import org.apache.http.HttpHeaders;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer;
+import org.apache.tinkerpop.gremlin.server.handler.TransactionManager;
+import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONTokens;
+import org.apache.tinkerpop.gremlin.util.Tokens;
+import org.apache.tinkerpop.gremlin.util.message.RequestMessage;
+import org.apache.tinkerpop.gremlin.util.message.ResponseMessage;
+import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4;
+import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4;
+import org.apache.tinkerpop.gremlin.util.ser.Serializers;
+import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
+import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.tinkerpop.gremlin.util.ser.SerTokens.TOKEN_DATA;
+import static org.apache.tinkerpop.gremlin.util.ser.SerTokens.TOKEN_RESULT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Server-side integration tests for HTTP transaction protocol.
+ * 
+ * These tests bypass the driver entirely and use a raw Apache HTTP client to 
hit the server's HTTP endpoint directly.
+ * This validates that the server returns the correct status codes and error 
messages independent of any client-side
+ * guards.
+ */
+public class GremlinServerHttpTransactionIntegrateTest extends 
AbstractGremlinServerIntegrationTest {
+    private final String GTX = "gtx";
+    private final ObjectMapper mapper = new ObjectMapper();
+    private CloseableHttpClient client;
+
+    @Before
+    public void createHttpClient() {
+        client = HttpClients.createDefault();
+    }
+
+    @After
+    public void closeHttpClient() throws Exception {
+        client.close();
+    }
+
+    @Override
+    public Settings overrideSettings(final Settings settings) {
+        settings.channelizer = HttpChannelizer.class.getName();
+        final String nameOfTest = name.getMethodName();
+        switch (nameOfTest) {
+            case "shouldRejectRequestWhenMaxConcurrentTransactionsExceeded":
+                settings.maxConcurrentTransactions = 1;
+                break;
+            case "shouldTimeoutFreeSlotUnderMaxConcurrentTransactions":
+                settings.maxConcurrentTransactions = 1;
+                settings.transactionTimeout = 1000;
+                break;
+            case "shouldTimeoutIdleTransactionWithNoOperations":
+                settings.transactionTimeout = 1;
+                break;
+            case "shouldTimeoutAndRejectLateCommit":
+            case "shouldTrackTransactionCountAccurately":
+                settings.transactionTimeout = 1000;
+                break;
+            case "shouldRollbackAbandonedTransaction":
+                settings.transactionTimeout = 300;
+                break;
+        }
+        return settings;
+    }
+
+    /**
+     * Sends a JSON POST request and returns the response. Caller must close 
the response.
+     */
+    private CloseableHttpResponse postJson(final CloseableHttpClient client, 
final String json) throws Exception {
+        final HttpPost post = new 
HttpPost(TestClientFactory.createURLString());
+        post.addHeader("Content-Type", "application/json");
+        post.setEntity(new StringEntity(json, Consts.UTF_8));
+        return client.execute(post);
+    }
+
+    /**
+     * Sends a begin transaction request for the given graph alias and returns 
the server-generated transaction ID.
+     */
+    private String beginTx(final CloseableHttpClient client, final String 
graphAlias) throws Exception {
+        try (final CloseableHttpResponse response = postJson(client,
+                "{\"gremlin\":\"g.tx().begin()\",\"g\":\"" + graphAlias + 
"\"}")) {
+            assertEquals(200, response.getStatusLine().getStatusCode());
+            final String txIdHeader = 
response.getFirstHeader(Tokens.Headers.TRANSACTION_ID).getValue();
+            final String json = EntityUtils.toString(response.getEntity());
+            final JsonNode node = mapper.readTree(json);
+            final String txIdBody = node.get(TOKEN_RESULT).
+                    get(TOKEN_DATA).
+                    get(GraphSONTokens.VALUEPROP).get(0).
+                    get(GraphSONTokens.VALUEPROP).get(1).
+                    asText();
+            assertNotNull(txIdHeader);
+            assertEquals(txIdHeader, txIdBody);
+            return txIdHeader;
+        }
+    }
+
+    /**
+     * Sends a traversal within an existing transaction.
+     */
+    private CloseableHttpResponse submitInTx(final CloseableHttpClient client,
+                                             final String txId,
+                                             final String gremlin,
+                                             final String graphAlias)
+            throws Exception {
+        return postJson(client,
+                "{\"gremlin\":\"" + gremlin + "\",\"g\":\"" + graphAlias + 
"\",\"transactionId\":\"" + txId + "\"}");
+    }
+
+    /**
+     * Sends a commit for an existing transaction.
+     */
+    private CloseableHttpResponse commitTx(final CloseableHttpClient client,
+                                           final String txId, final String 
graphAlias) throws Exception {
+        return postJson(client,
+                "{\"gremlin\":\"g.tx().commit()\",\"g\":\"" + graphAlias + 
"\",\"transactionId\":\"" + txId + "\"}");
+    }
+
+    /**
+     * Sends a rollback for an existing transaction.
+     */
+    private CloseableHttpResponse rollbackTx(final CloseableHttpClient client,
+                                             final String txId, final String 
graphAlias) throws Exception {
+        return postJson(client,
+                "{\"gremlin\":\"g.tx().rollback()\",\"g\":\"" + graphAlias + 
"\",\"transactionId\":\"" + txId + "\"}");
+    }
+
+    /**
+     * Sends a non-transactional traversal (no transactionId).
+     */
+    private CloseableHttpResponse submitNonTx(final CloseableHttpClient client,
+                                              final String gremlin, final 
String graphAlias) throws Exception {
+        return postJson(client,
+                "{\"gremlin\":\"" + gremlin + "\",\"g\":\"" + graphAlias + 
"\"}");
+    }
+
+    /**
+     * Extracts the integer count from a typical count() response.
+     */
+    private int extractCount(final CloseableHttpResponse response) throws 
Exception {
+        final String json = EntityUtils.toString(response.getEntity());
+        final JsonNode node = mapper.readTree(json);
+        return node.get("result").get(TOKEN_DATA)
+                .get(GraphSONTokens.VALUEPROP).get(0)
+                .get(GraphSONTokens.VALUEPROP).intValue();
+    }
+
+    /**
+     * Extracts the status message from the response body's status field.
+     */
+    private String extractStatusMessage(final CloseableHttpResponse response) 
throws Exception {
+        final String json = EntityUtils.toString(response.getEntity());
+        final JsonNode node = mapper.readTree(json);
+        return node.get("status").get("message").asText();
+    }
+
+    @Test
+    public void shouldNotBeginTransactionWithUserProvidedId() throws Exception 
{
+        final String txId = beginTx(client, GTX);
+
+        try (final CloseableHttpResponse r = postJson(client,
+                "{\"gremlin\":\"g.tx().begin()\",\"g\":\"" + GTX + 
"\",\"transactionId\":\"" + txId + "\"}")) {
+            // Depending on whether the transaction is still open on the 
server when the second request arrives, there
+            // may be two different errors that the server throws.
+            assertTrue(r.getStatusLine().getStatusCode() == 404 || 
r.getStatusLine().getStatusCode() == 400);
+            final String msg = extractStatusMessage(r);
+            assertTrue(msg.contains("Begin transaction request cannot have a 
user-supplied transactionId") ||
+                    msg.contains("Transaction not found"));

Review Comment:
   Out of curiosity, what are the conditions which lead to the "Transaction not 
found" response? The "Begin transaction request cannot have a user-supplied 
transactionId" message is much clearer.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to