This is an automated email from the ASF dual-hosted git repository. yiguolei pushed a commit to branch branch-4.2 in repository https://gitbox.apache.org/repos/asf/doris.git
commit 3f8decb3429d73bb971910425c2bffcda9c24a6d Author: Calvin Kirs <[email protected]> AuthorDate: Sat Oct 10 09:32:32 2026 +0800 [fix](fe) Fix HTTP system forwarding on follower FE (#68827) https://github.com/apache/doris/pull/68823 --- .../java/org/apache/doris/qe/FEOpExecutor.java | 9 +++-- .../doris/qe/FEOpExecutorMysqlProtocolTest.java | 24 ++++++++++++ .../test_http_rest_v1_system_follower.groovy | 45 ++++++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/FEOpExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/FEOpExecutor.java index fe0de92857d..21420b31c22 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/FEOpExecutor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/FEOpExecutor.java @@ -23,6 +23,7 @@ import org.apache.doris.common.AnalysisException; import org.apache.doris.common.ClientPool; import org.apache.doris.common.Config; import org.apache.doris.common.ErrorCode; +import org.apache.doris.mysql.DummyMysqlChannel; import org.apache.doris.mysql.MysqlCommand; import org.apache.doris.mysql.MysqlCursorFetchCompatibility; import org.apache.doris.mysql.MysqlProto; @@ -222,10 +223,10 @@ public class FEOpExecutor { // Propagate the client's CLIENT_DEPRECATE_EOF capability so the master FE // generates packets matching the original client's protocol expectations. - // Only a MySQL connection negotiates this capability and owns a MysqlChannel; - // an Arrow Flight SQL session has none, and leaving the field unset keeps the - // master on its default packet layout. - if (ctx.getConnectType() == ConnectType.MYSQL) { + // HTTP contexts use a dummy MySQL channel without a negotiated capability. + // Leave their protocol fields unset so the master uses its default packet layout. + if (ctx.getConnectType() == ConnectType.MYSQL + && !(ctx.getMysqlChannel() instanceof DummyMysqlChannel && ctx.getCapability() == null)) { params.setClientDeprecatedEOF(ctx.getMysqlChannel().clientDeprecatedEOF()); params.setMysqlCapability(ctx.getCapability().getFlags()); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/qe/FEOpExecutorMysqlProtocolTest.java b/fe/fe-core/src/test/java/org/apache/doris/qe/FEOpExecutorMysqlProtocolTest.java index a3e235f56f5..de0c3e2ee8e 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/qe/FEOpExecutorMysqlProtocolTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/qe/FEOpExecutorMysqlProtocolTest.java @@ -60,6 +60,7 @@ public class FEOpExecutorMysqlProtocolTest { TMasterOpRequest request = new TestFEOpExecutor(context).build(); Assertions.assertTrue(request.isClientDeprecatedEOF()); + Assertions.assertEquals(context.getCapability().getFlags(), request.getMysqlCapability()); Assertions.assertTrue(request.isCursorFetchRequested()); Assertions.assertEquals("8.2.0", request.getConnectAttributes().get("_client_version")); @@ -107,6 +108,29 @@ public class FEOpExecutorMysqlProtocolTest { } } + @Test + public void testHttpForwardRequestUsesDefaultMysqlCapability() throws Exception { + Env env = Mockito.mock(Env.class); + Mockito.when(env.getSelfNode()).thenReturn(new SystemInfoService.HostInfo("127.0.0.1", 9010)); + try (MockedStatic<Env> mockedEnv = Mockito.mockStatic(Env.class)) { + mockedEnv.when(Env::getCurrentEnv).thenReturn(env); + ConnectContext context = new ConnectContext(); + context.setCurrentUserIdentity(UserIdentity.createAnalyzedUserIdentWithIp("alice", "%")); + context.setRemoteIP("127.0.0.1"); + Assertions.assertNull(context.getCapability()); + + TMasterOpRequest request = new TestFEOpExecutor(context).build(); + Assertions.assertFalse(request.isSetClientDeprecatedEOF()); + Assertions.assertFalse(request.isSetMysqlCapability()); + + ConnectContext forwardedContext = createContext(); + ConnectProcessor.restoreForwardedMysqlContext(forwardedContext, request); + int expectedFlags = MysqlCapability.DEFAULT_CAPABILITY.getFlags() + & ~MysqlCapability.Flag.CLIENT_DEPRECATE_EOF.getFlagBit(); + Assertions.assertEquals(expectedFlags, forwardedContext.getCapability().getFlags()); + } + } + @Test public void testForwardedCapabilityAndMissingCursorFlag() throws Exception { int legacyFlags = MysqlCapability.DEFAULT_CAPABILITY.getFlags() diff --git a/regression-test/suites/auth_p0/test_http_rest_v1_system_follower.groovy b/regression-test/suites/auth_p0/test_http_rest_v1_system_follower.groovy new file mode 100644 index 00000000000..b928969f2f9 --- /dev/null +++ b/regression-test/suites/auth_p0/test_http_rest_v1_system_follower.groovy @@ -0,0 +1,45 @@ +// 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. + +import org.apache.doris.regression.suite.ClusterOptions + +suite("test_http_rest_v1_system_follower", "docker") { + def options = new ClusterOptions() + options.setFeNum(2) + options.setBeNum(1) + docker(options) { + def follower = cluster.getAllFrontends(true).find { !it.isMaster } + assertNotNull(follower) + + // Wait for the follower to replay the journal before authenticating over HTTP. + String followerJdbcUrl = "jdbc:mysql://${follower.host}:${follower.queryPort}/" + connect(context.config.jdbcUser, context.config.jdbcPassword, followerJdbcUrl) { + sql "SYNC" + } + + httpTest { + basicAuthorization "${context.config.jdbcUser}", "${context.config.jdbcPassword}" + endpoint "${follower.host}:${follower.httpPort}" + uri "/rest/v1/system?path=/" + op "get" + check { respCode, body -> + assertEquals(200, respCode) + assertEquals(0, parseJson(body).code) + } + } + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
