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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new d418004c86b branch-4.1:[fix](ldap) Improve LDAP authentication 
resiliency and diagnostics (#61673) (#63288)
d418004c86b is described below

commit d418004c86b7d300848387d1bba8c2dee5e882be
Author: seawinde <[email protected]>
AuthorDate: Wed May 20 16:44:02 2026 +0800

    branch-4.1:[fix](ldap) Improve LDAP authentication resiliency and 
diagnostics (#61673) (#63288)
    
    pr: #61673
    commitId: ab94e99f89b
---
 .../java/org/apache/doris/common/LdapConfig.java   | 31 ++++++++-
 .../mysql/authenticate/AuthenticatorManager.java   | 16 +++++
 .../mysql/authenticate/ldap/LdapAuthenticator.java | 38 ++++++++---
 .../doris/mysql/authenticate/ldap/LdapClient.java  | 69 +++++++++++++++++--
 .../doris/mysql/authenticate/ldap/LdapManager.java | 52 +++++++++++++--
 .../doris/mysql/privilege/UserPropertyMgr.java     |  5 ++
 .../doris/mysql/authenticate/TestLogAppender.java  | 77 ++++++++++++++++++++++
 .../authenticate/ldap/LdapAuthenticatorTest.java   | 28 ++++++++
 .../mysql/authenticate/ldap/LdapClientTest.java    | 65 ++++++++++++++++++
 .../mysql/authenticate/ldap/LdapManagerTest.java   | 31 +++++++++
 10 files changed, 392 insertions(+), 20 deletions(-)

diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java 
b/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java
index 881840696dc..be25b5af0a4 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/LdapConfig.java
@@ -87,7 +87,36 @@ public class LdapConfig extends ConfigBase {
     public static long ldap_cache_timeout_day = 30;
 
     /**
-     * LDAP pool configuration:
+     * LDAP read timeout in milliseconds.
+     * Controls the maximum time to wait for an LDAP response after a request 
is sent.
+     * Uses JNDI property "com.sun.jndi.ldap.read.timeout".
+     * Set to 0 for no timeout (not recommended). Default 5000ms.
+     */
+    @ConfigBase.ConfField
+    public static int ldap_read_timeout_ms = 5000;
+
+    /**
+     * LDAP connect timeout in milliseconds.
+     * Controls the maximum time to wait for establishing a TCP connection to 
the LDAP server.
+     * Uses JNDI property "com.sun.jndi.ldap.connect.timeout".
+     * Set to 0 for no timeout (not recommended). Default 5000ms.
+     */
+    @ConfigBase.ConfField
+    public static int ldap_connect_timeout_ms = 5000;
+
+    /**
+     * Whether to use connection pooling for LDAP search operations.
+     * When true (default), uses Spring PoolingContextSource with ldap_pool_* 
settings.
+     * When false, each LDAP search creates a fresh connection, avoiding dead 
connection
+     * detection cost (testOnBorrow can burn read_timeout discovering stale 
connections
+     * killed by firewalls/NAT idle timeout). Recommended to set false if 
experiencing
+     * intermittent ~5s LDAP search latency spikes.
+     */
+    @ConfigBase.ConfField
+    public static boolean ldap_search_use_pool = true;
+
+    /**
+     * LDAP pool configuration (only effective when ldap_search_use_pool = 
true):
      * 
https://docs.spring.io/spring-ldap/docs/2.3.3.RELEASE/reference/#pool-configuration
      */
     /**
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
index 8ba711e6655..e64970411d2 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/AuthenticatorManager.java
@@ -97,14 +97,30 @@ public class AuthenticatorManager {
                                 MysqlAuthPacket authPacket,
                                 MysqlHandshakePacket handshakePacket) throws 
IOException {
         Authenticator authenticator = chooseAuthenticator(userName);
+        boolean debugEnabled = LOG.isDebugEnabled();
+        long resolveStart = 0L;
+        if (debugEnabled) {
+            LOG.debug("AuthenticatorManager: user={}, authenticator={}",
+                    userName, authenticator.getClass().getSimpleName());
+            resolveStart = System.currentTimeMillis();
+        }
         Optional<Password> password = authenticator.getPasswordResolver()
                 .resolvePassword(context, channel, serializer, authPacket, 
handshakePacket);
         if (!password.isPresent()) {
             return false;
         }
+        if (debugEnabled) {
+            long resolveElapsed = System.currentTimeMillis() - resolveStart;
+            LOG.debug("resolvePassword: user={}, elapsed={}ms", userName, 
resolveElapsed);
+            resolveStart = System.currentTimeMillis();
+        }
         String remoteIp = context.getMysqlChannel().getRemoteIp();
         AuthenticateRequest request = new AuthenticateRequest(userName, 
password.get(), remoteIp);
         AuthenticateResponse response = authenticator.authenticate(request);
+        if (debugEnabled) {
+            long authenticateElapsed = System.currentTimeMillis() - 
resolveStart;
+            LOG.debug("authenticate: user={}, elapsed={}ms", userName, 
authenticateElapsed);
+        }
         if (!response.isSuccess()) {
             MysqlProto.sendResponsePacket(context);
             return false;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java
index cd9cef469d2..7ec518c3c08 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticator.java
@@ -59,6 +59,7 @@ public class LdapAuthenticator implements Authenticator {
      */
     @Override
     public AuthenticateResponse authenticate(AuthenticateRequest request) 
throws IOException {
+        long start = System.currentTimeMillis();
         if (LOG.isDebugEnabled()) {
             LOG.debug("user:{} start to ldap authenticate.", 
request.getUserName());
         }
@@ -67,7 +68,14 @@ public class LdapAuthenticator implements Authenticator {
             return AuthenticateResponse.failedResponse;
         }
         ClearPassword clearPassword = (ClearPassword) password;
-        return internalAuthenticate(clearPassword.getPassword(), 
request.getUserName(), request.getRemoteIp());
+        AuthenticateResponse response = 
internalAuthenticate(clearPassword.getPassword(),
+                request.getUserName(), request.getRemoteIp());
+        long elapsed = System.currentTimeMillis() - start;
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("LdapAuthenticator.authenticate: user={}, success={}, 
elapsed={}ms",
+                    request.getUserName(), response.isSuccess(), elapsed);
+        }
+        return response;
     }
 
     @Override
@@ -75,11 +83,14 @@ public class LdapAuthenticator implements Authenticator {
         if (qualifiedUser.equals(Auth.ROOT_USER) || 
qualifiedUser.equals(Auth.ADMIN_USER)) {
             return false;
         }
-        // Fixme Note: LdapManager should be managed internally within the 
Ldap plugin
-        // and not be placed inside the Env class. This ensures that 
Ldap-related
-        // logic and dependencies are encapsulated within the plugin, promoting
-        // better modularity and maintainability.
-        return 
Env.getCurrentEnv().getAuth().getLdapManager().doesUserExist(qualifiedUser);
+        long start = System.currentTimeMillis();
+        boolean result = 
Env.getCurrentEnv().getAuth().getLdapManager().doesUserExist(qualifiedUser);
+        long elapsed = System.currentTimeMillis() - start;
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("LdapAuthenticator.canDeal: user={}, result={}, 
elapsed={}ms",
+                    qualifiedUser, result, elapsed);
+        }
+        return result;
     }
 
     /**
@@ -99,12 +110,14 @@ public class LdapAuthenticator implements Authenticator {
         // check user password by ldap server.
         try {
             if 
(!Env.getCurrentEnv().getAuth().getLdapManager().checkUserPasswd(qualifiedUser, 
password)) {
-                LOG.info("user:{} use check LDAP password failed.", userName);
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("internalAuthenticate: user={}, success=false", 
userName);
+                }
                 ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, 
qualifiedUser, remoteIp, usePasswd);
                 return AuthenticateResponse.failedResponse;
             }
         } catch (Exception e) {
-            LOG.error("Check ldap password error.", e);
+            LOG.warn("internalAuthenticate failed: user={}", userName, e);
             return AuthenticateResponse.failedResponse;
         }
 
@@ -115,12 +128,17 @@ public class LdapAuthenticator implements Authenticator {
         AuthenticateResponse response = new AuthenticateResponse(true);
         if (userIdentities.isEmpty()) {
             response.setUserIdentity(tempUserIdentity);
+            response.setTemp(true);
             if (LOG.isDebugEnabled()) {
-                LOG.debug("User:{} does not exists in doris, login as 
temporary users.", userName);
+                LOG.debug("internalAuthenticate: user={}, tempUser=true, 
identity={}",
+                        userName, tempUserIdentity);
             }
-            response.setTemp(true);
         } else {
             response.setUserIdentity(userIdentities.get(0));
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("internalAuthenticate: user={}, tempUser=false, 
identity={}",
+                        userName, userIdentities.get(0));
+            }
         }
         if (LOG.isDebugEnabled()) {
             LOG.debug("ldap authentication success: identity:{}", 
response.getUserIdentity());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapClient.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapClient.java
index 5d03917f0d3..36fa90cd31b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapClient.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapClient.java
@@ -29,6 +29,7 @@ import com.google.common.collect.Lists;
 import lombok.Data;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.springframework.ldap.AuthenticationException;
 import org.springframework.ldap.core.DirContextOperations;
 import org.springframework.ldap.core.LdapTemplate;
 import org.springframework.ldap.core.support.AbstractContextMapper;
@@ -39,7 +40,9 @@ import org.springframework.ldap.query.LdapQuery;
 import org.springframework.ldap.support.LdapEncoder;
 import 
org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 // This class is used to connect to the LDAP service.
 public class LdapClient {
@@ -60,7 +63,15 @@ public class LdapClient {
         public ClientInfo(String ldapPassword) {
             this.ldapPassword = ldapPassword;
             setLdapTemplateNoPool(ldapPassword);
-            setLdapTemplatePool(ldapPassword);
+            if (LdapConfig.ldap_search_use_pool) {
+                setLdapTemplatePool(ldapPassword);
+            }
+        }
+
+        // Returns the LdapTemplate for search operations:
+        // pooled if ldap_search_use_pool=true, non-pooled otherwise.
+        public LdapTemplate getSearchTemplate() {
+            return ldapTemplatePool != null ? ldapTemplatePool : 
ldapTemplateNoPool;
         }
 
         private void setLdapTemplateNoPool(String ldapPassword) {
@@ -71,6 +82,7 @@ public class LdapClient {
             contextSource.setUrl(url);
             contextSource.setUserDn(LdapConfig.ldap_admin_name);
             contextSource.setPassword(ldapPassword);
+            setLdapTimeoutProperties(contextSource);
             contextSource.afterPropertiesSet();
             ldapTemplateNoPool = new LdapTemplate(contextSource);
             ldapTemplateNoPool.setIgnorePartialResultException(true);
@@ -84,7 +96,7 @@ public class LdapClient {
             contextSource.setUrl(url);
             contextSource.setUserDn(LdapConfig.ldap_admin_name);
             contextSource.setPassword(ldapPassword);
-            contextSource.setPooled(true);
+            setLdapTimeoutProperties(contextSource);
             contextSource.afterPropertiesSet();
 
             PoolingContextSource poolingContextSource = new 
PoolingContextSource();
@@ -109,6 +121,21 @@ public class LdapClient {
             return this.ldapPassword == null || 
!this.ldapPassword.equals(ldapPassword);
         }
 
+        private void setLdapTimeoutProperties(LdapContextSource contextSource) 
{
+            Map<String, Object> baseEnv = new HashMap<>();
+            if (LdapConfig.ldap_read_timeout_ms > 0) {
+                baseEnv.put("com.sun.jndi.ldap.read.timeout",
+                        String.valueOf(LdapConfig.ldap_read_timeout_ms));
+            }
+            if (LdapConfig.ldap_connect_timeout_ms > 0) {
+                baseEnv.put("com.sun.jndi.ldap.connect.timeout",
+                        String.valueOf(LdapConfig.ldap_connect_timeout_ms));
+            }
+            if (!baseEnv.isEmpty()) {
+                contextSource.setBaseEnvironmentProperties(baseEnv);
+            }
+        }
+
     }
 
     private void init() {
@@ -143,19 +170,37 @@ public class LdapClient {
 
     boolean checkPassword(String userName, String password) {
         init();
+        long start = System.currentTimeMillis();
         try {
             
clientInfo.getLdapTemplateNoPool().authenticate(org.springframework.ldap.query.LdapQueryBuilder.query()
                     .base(LdapConfig.ldap_user_basedn)
                     .filter(applyLoginFilter(LdapConfig.ldap_user_filter, 
userName)), password);
+            long elapsed = System.currentTimeMillis() - start;
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("LdapClient.checkPassword: user={}, success=true, 
elapsed={}ms",
+                        userName, elapsed);
+            }
             return true;
+        } catch (AuthenticationException e) {
+            long elapsed = System.currentTimeMillis() - start;
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("LdapClient.checkPassword: user={}, success=false, 
elapsed={}ms, "
+                                + "errorClass={}, errorMessage={}",
+                        userName, elapsed, e.getClass().getSimpleName(), 
e.getMessage());
+            }
+            return false;
         } catch (Exception e) {
-            LOG.info("ldap client checkPassword failed, userName: {}", 
userName, e);
+            long elapsed = System.currentTimeMillis() - start;
+            LOG.warn("LdapClient.checkPassword failed: user={}, elapsed={}ms, "
+                            + "errorClass={}, errorMessage={}",
+                    userName, elapsed, e.getClass().getSimpleName(), 
e.getMessage(), e);
             return false;
         }
     }
 
     // Search group DNs by 'member' attribution.
     List<String> getGroups(String userName) {
+        long start = System.currentTimeMillis();
         List<String> groups = Lists.newArrayList();
         if (LdapConfig.ldap_group_basedn.isEmpty()) {
             return groups;
@@ -190,6 +235,11 @@ public class LdapClient {
                 groups.add(strings[1]);
             }
         }
+        long elapsed = System.currentTimeMillis() - start;
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("LdapClient.getGroups: user={}, groups={}, elapsed={}ms",
+                    userName, groups.size(), elapsed);
+        }
         return groups;
     }
 
@@ -211,18 +261,27 @@ public class LdapClient {
 
     public List<String> getDn(LdapQuery query) {
         init();
+        long start = System.currentTimeMillis();
         try {
-            return clientInfo.getLdapTemplatePool().search(query,
+            List<String> result = clientInfo.getSearchTemplate().search(query,
                     new AbstractContextMapper<String>() {
                         protected String doMapFromContext(DirContextOperations 
ctx) {
                             return ctx.getNameInNamespace();
                         }
                     });
+            long elapsed = System.currentTimeMillis() - start;
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("LdapClient.getDn: base={}, elapsed={}ms, 
results={}",
+                        query.base(), elapsed, result == null ? 0 : 
result.size());
+            }
+            return result;
         } catch (Exception e) {
+            long elapsed = System.currentTimeMillis() - start;
             String msg
                     = "Failed to retrieve the user's Distinguished Name (DN),"
                     + "This may be due to incorrect LDAP configuration or an 
unset/incorrect LDAP admin password.";
-            LOG.error(msg, e);
+            LOG.warn("LdapClient.getDn failed: base={}, elapsed={}ms, 
error={}",
+                    query.base(), elapsed, e.getMessage(), e);
             ErrorReport.report(ErrorCode.ERROR_LDAP_CONFIGURATION_ERR);
             throw new RuntimeException(msg);
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java
index 5f6003cd6c1..da4665eb7d3 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/authenticate/ldap/LdapManager.java
@@ -81,14 +81,28 @@ public class LdapManager {
         if (!checkParam(fullName)) {
             return null;
         }
+        long start = System.currentTimeMillis();
         LdapUserInfo ldapUserInfo = getUserInfoFromCache(fullName);
         if (ldapUserInfo != null && !ldapUserInfo.checkTimeout()) {
+            long elapsed = System.currentTimeMillis() - start;
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("LdapManager.getUserInfo: user={}, cacheHit=true, 
elapsed={}ms",
+                        fullName, elapsed);
+            }
             return ldapUserInfo;
         }
         try {
-            return getUserInfoAndUpdateCache(fullName);
+            LdapUserInfo result = getUserInfoAndUpdateCache(fullName);
+            long elapsed = System.currentTimeMillis() - start;
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("LdapManager.getUserInfo: user={}, cacheHit=false, 
elapsed={}ms",
+                        fullName, elapsed);
+            }
+            return result;
         } catch (DdlException e) {
-            LOG.warn("getUserInfo for {} failed", fullName, e);
+            long elapsed = System.currentTimeMillis() - start;
+            LOG.warn("LdapManager.getUserInfo failed: user={}, elapsed={}ms",
+                    fullName, elapsed, e);
             return null;
         }
     }
@@ -97,11 +111,19 @@ public class LdapManager {
         if (!checkParam(fullName)) {
             return false;
         }
+        long start = System.currentTimeMillis();
         LdapUserInfo info = getUserInfo(fullName);
-        return !Objects.isNull(info) && info.isExists();
+        boolean exists = !Objects.isNull(info) && info.isExists();
+        long elapsed = System.currentTimeMillis() - start;
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("LdapManager.doesUserExist: user={}, exists={}, 
elapsed={}ms",
+                    fullName, exists, elapsed);
+        }
+        return exists;
     }
 
     public boolean checkUserPasswd(String fullName, String passwd) {
+        long start = System.currentTimeMillis();
         String userName = ClusterNamespace.getNameFromFullName(fullName);
         if (AuthenticateType.getAuthTypeConfig() != AuthenticateType.LDAP || 
Strings.isNullOrEmpty(userName)
                 || Objects.isNull(passwd)) {
@@ -109,13 +131,28 @@ public class LdapManager {
         }
         LdapUserInfo ldapUserInfo = getUserInfo(fullName);
         if (Objects.isNull(ldapUserInfo) || !ldapUserInfo.isExists()) {
+            long elapsed = System.currentTimeMillis() - start;
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("LdapManager.checkUserPasswd: user={}, 
result=user_not_found, elapsed={}ms",
+                        fullName, elapsed);
+            }
             return false;
         }
 
         if (ldapUserInfo.isSetPasswd() && 
ldapUserInfo.getPasswd().equals(passwd)) {
+            long elapsed = System.currentTimeMillis() - start;
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("LdapManager.checkUserPasswd: user={}, 
result=cached_passwd_match, elapsed={}ms",
+                        fullName, elapsed);
+            }
             return true;
         }
         boolean isRightPasswd = ldapClient.checkPassword(userName, passwd);
+        long elapsed = System.currentTimeMillis() - start;
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("LdapManager.checkUserPasswd: user={}, result={}, 
elapsed={}ms",
+                    fullName, isRightPasswd ? "ldap_auth_ok" : 
"ldap_auth_fail", elapsed);
+        }
         if (!isRightPasswd) {
             return false;
         }
@@ -132,8 +169,15 @@ public class LdapManager {
     }
 
     public Set<Role> getUserRoles(String fullName) {
+        long start = System.currentTimeMillis();
         LdapUserInfo info = getUserInfo(fullName);
-        return info == null ? Collections.emptySet() : info.getRoles();
+        Set<Role> roles = info == null ? Collections.emptySet() : 
info.getRoles();
+        long elapsed = System.currentTimeMillis() - start;
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("LdapManager.getUserRoles: user={}, roles={}, 
elapsed={}ms",
+                    fullName, roles.size(), elapsed);
+        }
+        return roles;
     }
 
     private boolean checkParam(String fullName) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
index e068182308e..4a4c09bacbb 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
@@ -270,6 +270,11 @@ public class UserPropertyMgr implements Writable {
      * If the authentication type is LDAP and the user exists in LDAP, return 
DEFAULT_USER_PROPERTY.
      * If the authentication type is not the default type, return 
DEFAULT_USER_PROPERTY.
      * Otherwise, return existProperty.
+     *
+     * Note: Previously this method called LdapManager.doesUserExist() to 
check LDAP,
+     * but that was redundant: when authentication_type=LDAP (non-default), 
the fallback
+     * condition already returns DEFAULT_USER_PROPERTY. The LDAP call caused 
runtime
+     * network queries that could hang under Auth read lock, blocking all new 
connections.
      */
     private UserProperty getPropertyIfNull(String qualifiedUser, UserProperty 
existProperty) {
         if (null != existProperty) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/TestLogAppender.java
 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/TestLogAppender.java
new file mode 100644
index 00000000000..c88e81b91ce
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/TestLogAppender.java
@@ -0,0 +1,77 @@
+// 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.doris.mysql.authenticate;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class TestLogAppender extends AbstractAppender implements AutoCloseable 
{
+    private final Logger logger;
+    private final Level originalLevel;
+    private final List<LogEvent> events = new CopyOnWriteArrayList<>();
+
+    private TestLogAppender(String name, Logger logger) {
+        super(name, null, PatternLayout.createDefaultLayout(), false, 
Property.EMPTY_ARRAY);
+        this.logger = logger;
+        this.originalLevel = logger.getLevel();
+    }
+
+    public static TestLogAppender attach(Class<?> loggerClass) {
+        return attach(loggerClass, Level.DEBUG);
+    }
+
+    public static TestLogAppender attach(Class<?> loggerClass, Level level) {
+        Logger logger = (Logger) LogManager.getLogger(loggerClass);
+        TestLogAppender appender = new TestLogAppender(
+                "TestLogAppender-" + loggerClass.getSimpleName() + "-" + 
System.nanoTime(), logger);
+        appender.start();
+        logger.addAppender(appender);
+        logger.setLevel(level);
+        return appender;
+    }
+
+    @Override
+    public void append(LogEvent event) {
+        events.add(event.toImmutable());
+    }
+
+    public boolean contains(Level level, String messageFragment) {
+        for (LogEvent event : events) {
+            if (event.getLevel().equals(level)
+                    && 
event.getMessage().getFormattedMessage().contains(messageFragment)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void close() {
+        logger.removeAppender(this);
+        logger.setLevel(originalLevel);
+        stop();
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java
index 99cbcdb5fad..33073f1145a 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapAuthenticatorTest.java
@@ -20,6 +20,7 @@ package org.apache.doris.mysql.authenticate.ldap;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.mysql.authenticate.AuthenticateRequest;
 import org.apache.doris.mysql.authenticate.AuthenticateResponse;
+import org.apache.doris.mysql.authenticate.TestLogAppender;
 import org.apache.doris.mysql.authenticate.password.ClearPassword;
 import org.apache.doris.mysql.authenticate.password.ClearPasswordResolver;
 import org.apache.doris.mysql.privilege.Auth;
@@ -27,6 +28,7 @@ import org.apache.doris.mysql.privilege.Auth;
 import com.google.common.collect.Lists;
 import mockit.Expectations;
 import mockit.Mocked;
+import org.apache.logging.log4j.Level;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -111,6 +113,20 @@ public class LdapAuthenticatorTest {
         Assert.assertFalse(response.isSuccess());
     }
 
+    @Test
+    public void testAuthenticateLogsInfoWithoutThreshold() throws IOException {
+        setCheckPassword(true);
+        setGetUserInDoris(true);
+        try (TestLogAppender appender = 
TestLogAppender.attach(LdapAuthenticator.class)) {
+            AuthenticateResponse response = 
ldapAuthenticator.authenticate(request);
+            Assert.assertTrue(response.isSuccess());
+            Assert.assertTrue(appender.contains(Level.DEBUG,
+                    "LdapAuthenticator.authenticate: user=user, success=true, 
elapsed="));
+            Assert.assertFalse(appender.contains(Level.WARN,
+                    "LdapAuthenticator.authenticate slow: user=user"));
+        }
+    }
+
     @Test
     public void testAuthenticateWithCheckPasswordException() throws 
IOException {
         setCheckPasswordException();
@@ -139,6 +155,18 @@ public class LdapAuthenticatorTest {
         Assert.assertFalse(ldapAuthenticator.canDeal("ss"));
     }
 
+    @Test
+    public void testCanDealLogsInfoWithoutThreshold() {
+        setLdapUserExist(true);
+        try (TestLogAppender appender = 
TestLogAppender.attach(LdapAuthenticator.class)) {
+            Assert.assertTrue(ldapAuthenticator.canDeal("ss"));
+            Assert.assertTrue(appender.contains(Level.DEBUG,
+                    "LdapAuthenticator.canDeal: user=ss, result=true, 
elapsed="));
+            Assert.assertFalse(appender.contains(Level.WARN,
+                    "LdapAuthenticator.canDeal slow: user=ss"));
+        }
+    }
+
     @Test
     public void testGetPasswordResolver() {
         Assert.assertTrue(ldapAuthenticator.getPasswordResolver() instanceof 
ClearPasswordResolver);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapClientTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapClientTest.java
index 7790816856f..26695c687c7 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapClientTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapClientTest.java
@@ -20,16 +20,22 @@ package org.apache.doris.mysql.authenticate.ldap;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.LdapConfig;
 import org.apache.doris.common.util.NetUtils;
+import org.apache.doris.mysql.authenticate.TestLogAppender;
 
 import mockit.Expectations;
 import mockit.Tested;
+import org.apache.logging.log4j.Level;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.springframework.ldap.core.LdapTemplate;
 import org.springframework.ldap.query.LdapQuery;
 import org.springframework.ldap.support.LdapEncoder;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
 
@@ -100,6 +106,45 @@ public class LdapClientTest {
         Assert.assertEquals(1, ldapClient.getGroups("zhangsan").size());
     }
 
+    @Test
+    public void testGetGroupsLogsInfoWithoutThreshold() {
+        List<String> userDns = Arrays.asList("uid=zhangsan,dc=example,dc=com");
+        List<String> groupDns = 
Arrays.asList("cn=groupName,ou=groups,dc=example,dc=com");
+        new Expectations(ldapClient) {
+            {
+                ldapClient.getDn((LdapQuery) any);
+                result = userDns;
+                result = groupDns;
+            }
+        };
+
+        try (TestLogAppender appender = 
TestLogAppender.attach(LdapClient.class)) {
+            Assert.assertEquals(1, ldapClient.getGroups("zhangsan").size());
+            Assert.assertTrue(appender.contains(Level.DEBUG,
+                    "LdapClient.getGroups: user=zhangsan, groups=1, 
elapsed="));
+            Assert.assertFalse(appender.contains(Level.WARN,
+                    "LdapClient.getGroups slow: user=zhangsan"));
+        }
+    }
+
+    @Test
+    public void testGetSearchTemplateUsesNoPoolWhenDisabled() throws Exception 
{
+        LdapConfig.ldap_search_use_pool = false;
+
+        Object clientInfo = newClientInfo("secret");
+        Assert.assertSame(getFieldValue(clientInfo, "ldapTemplateNoPool"), 
getSearchTemplate(clientInfo));
+        Assert.assertNull(getFieldValue(clientInfo, "ldapTemplatePool"));
+    }
+
+    @Test
+    public void testGetSearchTemplateUsesPoolWhenEnabled() throws Exception {
+        LdapConfig.ldap_search_use_pool = true;
+
+        Object clientInfo = newClientInfo("secret");
+        Assert.assertNotNull(getFieldValue(clientInfo, "ldapTemplatePool"));
+        Assert.assertSame(getFieldValue(clientInfo, "ldapTemplatePool"), 
getSearchTemplate(clientInfo));
+    }
+
     @Test
     public void testSecuredProtocolIsUsed() {
         //testing default case with not specified property ldap_use_ssl or it 
is specified as false
@@ -151,5 +196,25 @@ public class LdapClientTest {
     @After
     public void tearDown() {
         LdapConfig.ldap_use_ssl = false; // restoring default value for other 
tests
+        LdapConfig.ldap_search_use_pool = true;
+    }
+
+    private Object newClientInfo(String ldapPassword) throws Exception {
+        Class<?> clientInfoClass = 
Class.forName("org.apache.doris.mysql.authenticate.ldap.LdapClient$ClientInfo");
+        Constructor<?> constructor = 
clientInfoClass.getDeclaredConstructor(String.class);
+        constructor.setAccessible(true);
+        return constructor.newInstance(ldapPassword);
+    }
+
+    private LdapTemplate getSearchTemplate(Object clientInfo) throws Exception 
{
+        Method method = 
clientInfo.getClass().getDeclaredMethod("getSearchTemplate");
+        method.setAccessible(true);
+        return (LdapTemplate) method.invoke(clientInfo);
+    }
+
+    private Object getFieldValue(Object clientInfo, String fieldName) throws 
Exception {
+        Field field = clientInfo.getClass().getDeclaredField(fieldName);
+        field.setAccessible(true);
+        return field.get(clientInfo);
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java
index 8af499bbbe8..70361675054 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/authenticate/ldap/LdapManagerTest.java
@@ -18,9 +18,11 @@
 package org.apache.doris.mysql.authenticate.ldap;
 
 import org.apache.doris.common.Config;
+import org.apache.doris.mysql.authenticate.TestLogAppender;
 
 import mockit.Expectations;
 import mockit.Mocked;
+import org.apache.logging.log4j.Level;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -85,4 +87,33 @@ public class LdapManagerTest {
         mockClient(true, false);
         Assert.assertFalse(ldapManager.checkUserPasswd(USER2, "123"));
     }
+
+    @Test
+    public void testCheckUserPasswdCachedPasswdMatchLogsInfoWithoutThreshold() 
{
+        LdapManager ldapManager = new LdapManager();
+        mockClient(true, true);
+        Assert.assertTrue(ldapManager.checkUserPasswd(USER1, "123"));
+
+        try (TestLogAppender appender = 
TestLogAppender.attach(LdapManager.class)) {
+            Assert.assertTrue(ldapManager.checkUserPasswd(USER1, "123"));
+            Assert.assertTrue(appender.contains(Level.DEBUG,
+                    "LdapManager.checkUserPasswd: user=user1, 
result=cached_passwd_match, elapsed="));
+            Assert.assertFalse(appender.contains(Level.WARN,
+                    "LdapManager.checkUserPasswd slow: user=user1"));
+        }
+    }
+
+    @Test
+    public void testGetUserInfoLogsInfoWithoutThreshold() {
+        LdapManager ldapManager = new LdapManager();
+        mockClient(true, true);
+
+        try (TestLogAppender appender = 
TestLogAppender.attach(LdapManager.class)) {
+            Assert.assertNotNull(ldapManager.getUserInfo(USER1));
+            Assert.assertTrue(appender.contains(Level.DEBUG,
+                    "LdapManager.getUserInfo: user=user1, cacheHit=false, 
elapsed="));
+            Assert.assertFalse(appender.contains(Level.WARN,
+                    "LdapManager.getUserInfo slow: user=user1"));
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to