You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2022/02/17 13:52:11 UTC

[ignite] branch master updated: IGNITE-13389 Thin client: optionally append server exception stack trace to error message (#9824)

This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 2311878  IGNITE-13389 Thin client: optionally append server exception stack trace to error message (#9824)
2311878 is described below

commit 2311878802b57a50a07b842447fa86f9dbc059a7
Author: Sergei Ryzhov <s....@gmail.com>
AuthorDate: Thu Feb 17 16:50:23 2022 +0300

    IGNITE-13389 Thin client: optionally append server exception stack trace to error message (#9824)
    
    * Add `ThinClientConfiguration.sendServerExceptionStackTraceToClient`, default false. When true, include exception stack trace with error message in thin client response.
    * Add `DistributedThinClientConfiguration` and `ClientProcessorMXBean.showFullStackOnClientSide` to control the behavior for the entire cluster at runtime.
    
    Co-authored-by: zstan <st...@gmail.com>
---
 .../configuration/ThinClientConfiguration.java     | 18 +++++
 .../processors/cache/GridCacheMapEntry.java        |  9 ++-
 .../DistributedThinClientConfiguration.java        | 81 ++++++++++++++++++++
 .../processors/odbc/ClientListenerProcessor.java   | 27 +++++++
 .../platform/client/ClientRequestHandler.java      |  6 ++
 .../ignite/mxbean/ClientProcessorMXBean.java       | 10 +++
 .../org/apache/ignite/client/IgniteBinaryTest.java | 89 ++++++++++++++++++++--
 7 files changed, 231 insertions(+), 9 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/ThinClientConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/ThinClientConfiguration.java
index 5ff3410..dbafffd 100644
--- a/modules/core/src/main/java/org/apache/ignite/configuration/ThinClientConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/ThinClientConfiguration.java
@@ -37,6 +37,9 @@ public class ThinClientConfiguration {
     /** Active compute tasks per connection limit. */
     private int maxActiveComputeTasksPerConn = DFLT_MAX_ACTIVE_COMPUTE_TASKS_PER_CONNECTION;
 
+    /** If {@code true} sends a server exception stack trace to the client side. */
+    private boolean sendServerExcStackTraceToClient;
+
     /**
      * Creates thin-client configuration with all default values.
      */
@@ -54,6 +57,7 @@ public class ThinClientConfiguration {
 
         maxActiveTxPerConn = cfg.maxActiveTxPerConn;
         maxActiveComputeTasksPerConn = cfg.maxActiveComputeTasksPerConn;
+        sendServerExcStackTraceToClient = cfg.sendServerExcStackTraceToClient;
     }
 
     /**
@@ -97,6 +101,20 @@ public class ThinClientConfiguration {
         return this;
     }
 
+    /**
+     * @return If {@code true} sends a server exception stack to the client side.
+     */
+    public boolean sendServerExceptionStackTraceToClient() {
+        return sendServerExcStackTraceToClient;
+    }
+
+    /**
+     * @param sendServerExcStackTraceToClient If {@code true} sends a server exception stack to the client side.
+     */
+    public void sendServerExceptionStackTraceToClient(boolean sendServerExcStackTraceToClient) {
+        this.sendServerExcStackTraceToClient = sendServerExcStackTraceToClient;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(ThinClientConfiguration.class, this);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
index f2edcd4..456f652 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
@@ -6438,7 +6438,14 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
                 CacheLazyEntry<Object, Object> interceptEntry =
                     new CacheLazyEntry<>(cctx, entry.key, null, oldVal, null, keepBinary);
 
-                Object interceptorVal = cctx.config().getInterceptor().onBeforePut(interceptEntry, updated0);
+                Object interceptorVal = null;
+
+                try {
+                    interceptorVal = cctx.config().getInterceptor().onBeforePut(interceptEntry, updated0);
+                }
+                catch (Throwable e) {
+                    throw new IgniteCheckedException(e);
+                }
 
                 wasIntercepted = true;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/configuration/distributed/DistributedThinClientConfiguration.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/configuration/distributed/DistributedThinClientConfiguration.java
new file mode 100644
index 0000000..ff41dca
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/configuration/distributed/DistributedThinClientConfiguration.java
@@ -0,0 +1,81 @@
+/*
+ * 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.ignite.internal.processors.configuration.distributed;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
+import org.apache.ignite.internal.util.future.GridFutureAdapter;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.cluster.DistributedConfigurationUtils.makeUpdateListener;
+import static org.apache.ignite.internal.processors.configuration.distributed.DistributedBooleanProperty.detachedBooleanProperty;
+
+/**
+ * Thin client distributed configuration.
+ */
+public class DistributedThinClientConfiguration {
+    /** */
+    private final IgniteLogger log;
+
+    /** . */
+    private final DistributedChangeableProperty<Boolean> showStackTrace =
+        detachedBooleanProperty("thinClientProperty.showStackTrace");
+
+    /** Message of baseline auto-adjust parameter was changed. */
+    private static final String PROPERTY_UPDATE_MESSAGE =
+        "ThinClientProperty parameter '%s' was changed from '%s' to '%s'";
+
+    /**
+     * @param ctx Kernal context.
+     */
+    public DistributedThinClientConfiguration(
+        GridKernalContext ctx
+    ) {
+        log = ctx.log(DistributedThinClientConfiguration.class);
+
+        GridInternalSubscriptionProcessor isp = ctx.internalSubscriptionProcessor();
+
+        isp.registerDistributedConfigurationListener(
+            new DistributedConfigurationLifecycleListener() {
+                @Override public void onReadyToRegister(DistributedPropertyDispatcher dispatcher) {
+                    showStackTrace.addListener(makeUpdateListener(PROPERTY_UPDATE_MESSAGE, log));
+
+                    dispatcher.registerProperties(showStackTrace);
+                }
+            }
+        );
+    }
+
+    /**
+     * @param showStack If {@code true} shows full stack trace on the client side.
+     * @return Future for update operation.
+     */
+    public GridFutureAdapter<?> updateThinClientSendServerStackTraceAsync(boolean showStack) throws IgniteCheckedException {
+        return showStackTrace.propagateAsync(showStack);
+    }
+
+    /**
+     * @return If {@code true}, thin client response will include full stack trace when exception occurs.
+     * When {@code false}, only top level exception message is included.
+     */
+    @Nullable public Boolean sendServerExceptionStackTraceToClient() {
+        return showStackTrace.get();
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
index b0a260f..877644b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
@@ -30,6 +30,7 @@ import javax.management.JMException;
 import javax.management.ObjectName;
 import javax.net.ssl.SSLContext;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
 import org.apache.ignite.configuration.ClientConnectorConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.configuration.OdbcConfiguration;
@@ -37,6 +38,7 @@ import org.apache.ignite.configuration.SqlConnectorConfiguration;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.managers.systemview.walker.ClientConnectionViewWalker;
 import org.apache.ignite.internal.processors.GridProcessorAdapter;
+import org.apache.ignite.internal.processors.configuration.distributed.DistributedThinClientConfiguration;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext;
 import org.apache.ignite.internal.processors.odbc.odbc.OdbcConnectionContext;
 import org.apache.ignite.internal.util.GridSpinBusyLock;
@@ -90,6 +92,9 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
     /** Executor service. */
     private ExecutorService execSvc;
 
+    /** Thin client distributed configuration. */
+    private DistributedThinClientConfiguration distrThinCfg;
+
     /**
      * @param ctx Kernal context.
      */
@@ -198,6 +203,8 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
                     new ClientConnectionViewWalker(),
                     srv.sessions(),
                     ClientConnectionView::new);
+
+                distrThinCfg = new DistributedThinClientConfiguration(ctx);
             }
             catch (Exception e) {
                 throw new IgniteCheckedException("Failed to start client connector processor.", e);
@@ -559,6 +566,16 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
     }
 
     /**
+     * @return If {@code true} sends a server exception stack to the client side.
+     */
+    public boolean sendServerExceptionStackTraceToClient() {
+        Boolean send = distrThinCfg.sendServerExceptionStackTraceToClient();
+
+        return send == null ?
+            ctx.config().getClientConnectorConfiguration().getThinClientConfiguration().sendServerExceptionStackTraceToClient() : send;
+    }
+
+    /**
      * ClientProcessorMXBean interface.
      */
     private class ClientProcessorMXBeanImpl implements ClientProcessorMXBean {
@@ -620,5 +637,15 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
 
             return false;
         }
+
+        /** {@inheritDoc} */
+        @Override public void showFullStackOnClientSide(boolean show) {
+            try {
+                distrThinCfg.updateThinClientSendServerStackTraceAsync(show).get();
+            }
+            catch (IgniteCheckedException e) {
+                throw new IgniteException(e);
+            }
+        }
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
index 7510dfc..d086a57 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
@@ -32,6 +32,8 @@ import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheSq
 import org.apache.ignite.internal.processors.platform.client.cache.ClientCacheSqlQueryRequest;
 import org.apache.ignite.internal.processors.platform.client.tx.ClientTxAwareRequest;
 import org.apache.ignite.internal.processors.platform.client.tx.ClientTxContext;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.plugin.security.SecurityException;
 
 import static org.apache.ignite.internal.processors.platform.client.ClientProtocolVersionFeature.BITMAP_FEATURES;
@@ -123,6 +125,10 @@ public class ClientRequestHandler implements ClientListenerRequestHandler {
             msg = sqlState + ": " + msg;
         }
 
+        if ((X.getCause(e) != null || !X.getSuppressedList(e).isEmpty())
+                && ctx.kernalContext().sqlListener().sendServerExceptionStackTraceToClient())
+            msg = msg + U.nl() + X.getFullStackTrace(e);
+
         return new ClientResponse(req.requestId(), status, msg);
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/mxbean/ClientProcessorMXBean.java b/modules/core/src/main/java/org/apache/ignite/mxbean/ClientProcessorMXBean.java
index 5da7eee..93375af 100644
--- a/modules/core/src/main/java/org/apache/ignite/mxbean/ClientProcessorMXBean.java
+++ b/modules/core/src/main/java/org/apache/ignite/mxbean/ClientProcessorMXBean.java
@@ -48,4 +48,14 @@ public interface ClientProcessorMXBean {
     public boolean dropConnection(
         @MXBeanParameter(name = "id", description = "Client connection ID.") long id
     );
+
+    /**
+     * If sets to {@code true} shows full stack trace otherwise highlevel short error message.
+     *
+     * @param show Show flag.
+     */
+    @MXBeanDescription("Show error full stack.")
+    void showFullStackOnClientSide(
+        @MXBeanParameter(name = "show", description = "Show error full stack.") boolean show
+    );
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/client/IgniteBinaryTest.java b/modules/core/src/test/java/org/apache/ignite/client/IgniteBinaryTest.java
index f7681fa..6e019c8 100644
--- a/modules/core/src/test/java/org/apache/ignite/client/IgniteBinaryTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/client/IgniteBinaryTest.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 import java.util.stream.Collectors;
+import javax.cache.Cache;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteBinary;
 import org.apache.ignite.IgniteCache;
@@ -35,14 +36,20 @@ import org.apache.ignite.binary.BinarySerializer;
 import org.apache.ignite.binary.BinaryType;
 import org.apache.ignite.binary.BinaryTypeConfiguration;
 import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.cache.CacheInterceptorAdapter;
 import org.apache.ignite.configuration.BinaryConfiguration;
+import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.binary.BinaryObjectImpl;
 import org.apache.ignite.internal.binary.GridBinaryMarshaller;
-import org.apache.ignite.testframework.GridTestUtils;
-import org.junit.Rule;
+import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.mxbean.ClientProcessorMXBean;
+import org.apache.ignite.testframework.ListeningTestLogger;
+import org.apache.ignite.testframework.LogListener;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
-import org.junit.rules.Timeout;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -51,11 +58,7 @@ import static org.junit.Assert.assertNull;
 /**
  * Ignite {@link BinaryObject} API system tests.
  */
-public class IgniteBinaryTest {
-    /** Per test timeout */
-    @Rule
-    public Timeout globalTimeout = new Timeout((int)GridTestUtils.DFLT_TEST_TIMEOUT);
-
+public class IgniteBinaryTest extends GridCommonAbstractTest {
     /**
      * Unmarshalling schema-less Ignite binary objects into Java static types.
      */
@@ -134,6 +137,76 @@ public class IgniteBinaryTest {
     }
 
     /**
+     * Tests that {@code org.apache.ignite.cache.CacheInterceptor#onBeforePut(javax.cache.Cache.Entry, java.lang.Object)}
+     * throws correct exception in case while cache operations are called from thin client. Only BinaryObject`s are
+     * acceptable in this case.
+     */
+    @Test
+    public void testBinaryWithNotGenericInterceptor() throws Exception {
+        IgniteConfiguration ccfg = Config.getServerConfiguration()
+            .setCacheConfiguration(new CacheConfiguration("test").setInterceptor(new ThinBinaryValueInterceptor()));
+
+        String castErr = "cannot be cast to";
+        String treeErr = "B+Tree is corrupted";
+
+        ListeningTestLogger srvLog = new ListeningTestLogger(log);
+
+        LogListener lsnrCast = LogListener.matches(castErr).
+            andMatches(str -> !str.contains(treeErr)).build();
+
+        srvLog.registerListener(lsnrCast);
+
+        ccfg.setGridLogger(srvLog);
+
+        try (Ignite ign = Ignition.start(ccfg)) {
+            try (IgniteClient client =
+                     Ignition.startClient(new ClientConfiguration().setAddresses(Config.SERVER))
+            ) {
+                ClientCache<Integer, ThinBinaryValue> cache = client.cache("test");
+
+                try {
+                    cache.put(1, new ThinBinaryValue());
+
+                    fail();
+                }
+                catch (Exception e) {
+                    assertFalse(X.getFullStackTrace(e).contains(castErr));
+                }
+
+                ClientProcessorMXBean serverMxBean =
+                    getMxBean(ign.name(), "Clients", ClientListenerProcessor.class, ClientProcessorMXBean.class);
+
+                serverMxBean.showFullStackOnClientSide(true);
+
+                try {
+                    cache.put(1, new ThinBinaryValue());
+                }
+                catch (Exception e) {
+                    assertTrue(X.getFullStackTrace(e).contains(castErr));
+                }
+            }
+        }
+
+        assertTrue(lsnrCast.check());
+    }
+
+    /**
+     * Test interceptor implementation.
+     */
+    private static class ThinBinaryValueInterceptor extends CacheInterceptorAdapter<String, ThinBinaryValue> {
+        /** {@inheritDoc} */
+        @Override public ThinBinaryValue onBeforePut(Cache.Entry<String, ThinBinaryValue> entry, ThinBinaryValue newVal) {
+            return super.onBeforePut(entry, newVal);
+        }
+    }
+
+    /**
+     * Test value class.
+     */
+    private static class ThinBinaryValue {
+    }
+
+    /**
      * Check that binary types are registered for nested types too.
      * With enabled "CompactFooter" binary type schema also should be passed to server.
      */