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

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


The following commit(s) were added to refs/heads/master by this push:
     new 056e765d2e5 [feature](docker suite) make docker suite no need external 
doris cluster (#40787)
056e765d2e5 is described below

commit 056e765d2e5384695df4abb1ecb60e9642fc83a8
Author: yujun <yu.jun.re...@gmail.com>
AuthorDate: Thu Sep 26 22:45:22 2024 +0800

    [feature](docker suite) make docker suite no need external doris cluster 
(#40787)
    
    1.  Make docker suite no need external doris cluster:
    a. run docker suite according to ClusterOptions.cloudMode = true/false;
    b. if ClusterOptions.cloudMode = null, user can set it with cmd: `sh
    run-regression-test.sh --run docker_action -runMode=cloud/not_cloud`
    
    2. refactor database.py, simplifier its logic;
    
    3.  doris compose add cmd options -v for debug log.
    
    4.  doris compose print flush buffer.
---
 docker/runtime/doris-compose/Dockerfile            |   2 +-
 docker/runtime/doris-compose/Readme.md             |  19 +++
 docker/runtime/doris-compose/cluster.py            |   6 +-
 docker/runtime/doris-compose/command.py            |  33 +++---
 docker/runtime/doris-compose/database.py           | 129 +++++++++------------
 docker/runtime/doris-compose/doris-compose.py      |   9 +-
 .../{requirements.txt => format-code.sh}           |  10 +-
 docker/runtime/doris-compose/requirements.txt      |   1 +
 docker/runtime/doris-compose/resource/common.sh    |  11 +-
 docker/runtime/doris-compose/resource/init_fe.sh   |   2 +-
 docker/runtime/doris-compose/utils.py              |   4 +
 .../org/apache/doris/regression/Config.groovy      |  69 ++++++++---
 .../apache/doris/regression/ConfigOptions.groovy   |  10 ++
 .../doris/regression/action/ProfileAction.groovy   |   4 +-
 .../org/apache/doris/regression/suite/Suite.groovy |  29 ++++-
 .../doris/regression/suite/SuiteCluster.groovy     |   4 -
 .../suites/demo_p0/docker_action.groovy            |  25 +++-
 .../test_abort_txn_by_be_local5.groovy             |   1 -
 .../test_abort_txn_by_be_local6.groovy             |   1 -
 .../test_abort_txn_by_fe_local3.groovy             |   1 -
 run-regression-test.sh                             |   2 +
 21 files changed, 229 insertions(+), 143 deletions(-)

diff --git a/docker/runtime/doris-compose/Dockerfile 
b/docker/runtime/doris-compose/Dockerfile
index 2aabe196205..48d94d612df 100644
--- a/docker/runtime/doris-compose/Dockerfile
+++ b/docker/runtime/doris-compose/Dockerfile
@@ -38,7 +38,7 @@ RUN  sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g 
/etc/apt/sources.list
 RUN  apt-get clean
 
 RUN apt-get update && \
-    apt-get install -y default-mysql-client python lsof tzdata curl unzip 
patchelf jq procps && \
+    apt-get install -y default-mysql-client python lsof tzdata curl unzip 
patchelf jq procps util-linux && \
     ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
     dpkg-reconfigure -f noninteractive tzdata && \
     apt-get clean
diff --git a/docker/runtime/doris-compose/Readme.md 
b/docker/runtime/doris-compose/Readme.md
index 770414f7a2b..c4c4dc0990f 100644
--- a/docker/runtime/doris-compose/Readme.md
+++ b/docker/runtime/doris-compose/Readme.md
@@ -154,3 +154,22 @@ steps:
 2. Generate regression-conf-custom.groovy: `python 
docker/runtime/doris-compose/doris-compose.py config my-cluster  
<doris-root-path> --connect-follow-fe`
 3. Run regression test: `bash run-regression-test.sh --run -times 1 -parallel 
1 -suiteParallel 1 -d cloud/multi_cluster`
 
+## Problem investigation
+
+#### Log
+
+Each cluster has logs in /tmp/doris/{cluster-name}/{node-xxx}/log. For each 
node, doris compose will also print log in 
/tmp/doris/{cluster-name}/{node-xxx}/log/health.out
+
+#### Up cluster using non-detach mode
+
+```
+python docker/runtime/doris-compose/doris-compose.py up ...   -no-detach
+```
+
+## Developer
+
+Before submitting code, pls format code.
+
+```
+bash format-code.sh
+```
diff --git a/docker/runtime/doris-compose/cluster.py 
b/docker/runtime/doris-compose/cluster.py
index 3a2d95ac986..985ef27113b 100644
--- a/docker/runtime/doris-compose/cluster.py
+++ b/docker/runtime/doris-compose/cluster.py
@@ -16,7 +16,6 @@
 # under the License.
 
 import filelock
-import json
 import jsonpickle
 import os
 import os.path
@@ -405,11 +404,12 @@ class FE(Node):
         if self.cluster.is_cloud:
             cfg += [
                 "meta_service_endpoint = {}".format(
-                    self.cluster.get_meta_server_addr()), "",
+                    self.cluster.get_meta_server_addr()),
+                "",
                 "# For regression-test",
                 "ignore_unsupported_properties_in_cloud_mode = true",
                 "merge_on_write_forced_to_false = true",
-                "deploy_mode = cloud"
+                "deploy_mode = cloud",
             ]
 
             if self.cluster.sql_mode_node_mgr:
diff --git a/docker/runtime/doris-compose/command.py 
b/docker/runtime/doris-compose/command.py
index 3a5afc714dd..48863003223 100644
--- a/docker/runtime/doris-compose/command.py
+++ b/docker/runtime/doris-compose/command.py
@@ -92,7 +92,12 @@ class Command(object):
     def run(self, args):
         raise Exception("No implemented")
 
-    def _add_parser_output_json(self, parser):
+    def _add_parser_common_args(self, parser):
+        parser.add_argument("-v",
+                            "--verbose",
+                            default=False,
+                            action=self._get_parser_bool_action(True),
+                            help="verbose logging.")
         parser.add_argument("--output-json",
                             default=False,
                             action=self._get_parser_bool_action(True),
@@ -150,7 +155,7 @@ class SimpleCommand(Command):
         parser = args_parsers.add_parser(self.command, help=help)
         parser.add_argument("NAME", help="Specify cluster name.")
         self._add_parser_ids_args(parser)
-        self._add_parser_output_json(parser)
+        self._add_parser_common_args(parser)
 
     def run(self, args):
         cluster = CLUSTER.Cluster.load(args.NAME)
@@ -180,6 +185,7 @@ class UpCommand(Command):
                             nargs="?",
                             help="Specify docker image.")
 
+        self._add_parser_common_args(parser)
         parser.add_argument(
             "--cloud",
             default=False,
@@ -197,8 +203,6 @@ class UpCommand(Command):
             "> 0 max wait seconds, -1 wait unlimited."
         )
 
-        self._add_parser_output_json(parser)
-
         group1 = parser.add_argument_group("add new nodes",
                                            "add cluster nodes.")
         group1.add_argument(
@@ -325,16 +329,14 @@ class UpCommand(Command):
                 "--be-cluster-id",
                 default=True,
                 action=self._get_parser_bool_action(False),
-                help=
-                "Do not set BE cluster ID in conf. Default is False.")
+                help="Do not set BE cluster ID in conf. Default is False.")
         else:
             parser.add_argument(
                 "--no-be-cluster-id",
                 dest='be_cluster_id',
                 default=True,
                 action=self._get_parser_bool_action(False),
-                help=
-                "Do not set BE cluser ID in conf. Default is False.")
+                help="Do not set BE cluser ID in conf. Default is False.")
 
         parser.add_argument(
             "--fdb-version",
@@ -669,7 +671,7 @@ class DownCommand(Command):
                                            "then apply to all containers.")
         parser.add_argument("NAME", help="Specify cluster name")
         self._add_parser_ids_args(parser)
-        self._add_parser_output_json(parser)
+        self._add_parser_common_args(parser)
         parser.add_argument(
             "--clean",
             default=False,
@@ -782,12 +784,9 @@ class ListNode(object):
         self.created = ""
         self.alive = ""
         self.is_master = ""
-        self.query_port = ""
         self.tablet_num = ""
         self.last_heartbeat = ""
         self.err_msg = ""
-        self.edit_log_port = 0
-        self.heartbeat_port = 0
 
     def info(self, detail):
         result = [
@@ -825,10 +824,8 @@ class ListNode(object):
             if fe:
                 self.alive = str(fe.alive).lower()
                 self.is_master = str(fe.is_master).lower()
-                self.query_port = fe.query_port
                 self.last_heartbeat = fe.last_heartbeat
                 self.err_msg = fe.err_msg
-                self.edit_log_port = fe.edit_log_port
         elif self.node_type == CLUSTER.Node.TYPE_BE:
             self.backend_id = -1
             be = db_mgr.get_be(self.id)
@@ -838,7 +835,6 @@ class ListNode(object):
                 self.tablet_num = be.tablet_num
                 self.last_heartbeat = be.last_heartbeat
                 self.err_msg = be.err_msg
-                self.heartbeat_port = be.heartbeat_port
 
 
 class GenConfCommand(Command):
@@ -977,7 +973,7 @@ class ListCommand(Command):
             help=
             "Specify multiple clusters, if specific, show all their 
containers."
         )
-        self._add_parser_output_json(parser)
+        self._add_parser_common_args(parser)
         parser.add_argument("--detail",
                             default=False,
                             action=self._get_parser_bool_action(True),
@@ -1021,7 +1017,8 @@ class ListCommand(Command):
                 if services is None:
                     return COMPOSE_BAD, {}
                 return COMPOSE_GOOD, {
-                    service: ComposeService(
+                    service:
+                    ComposeService(
                         service,
                         list(service_conf["networks"].values())[0]
                         ["ipv4_address"], service_conf["image"])
@@ -1186,7 +1183,7 @@ class GetCloudIniCommand(Command):
             help=
             "Specify multiple clusters, if specific, show all their 
containers."
         )
-        self._add_parser_output_json(parser)
+        self._add_parser_common_args(parser)
 
     def _handle_data(self, header, datas):
         if utils.is_enable_log():
diff --git a/docker/runtime/doris-compose/database.py 
b/docker/runtime/doris-compose/database.py
index bbf6fb4fbeb..46cdd961c9f 100644
--- a/docker/runtime/doris-compose/database.py
+++ b/docker/runtime/doris-compose/database.py
@@ -27,21 +27,18 @@ LOG = utils.get_logger()
 
 class FEState(object):
 
-    def __init__(self, id, query_port, is_master, alive, last_heartbeat,
-                 err_msg, edit_log_port):
+    def __init__(self, id, is_master, alive, last_heartbeat, err_msg):
         self.id = id
-        self.query_port = query_port
         self.is_master = is_master
         self.alive = alive
         self.last_heartbeat = last_heartbeat
         self.err_msg = err_msg
-        self.edit_log_port = edit_log_port
 
 
 class BEState(object):
 
     def __init__(self, id, backend_id, decommissioned, alive, tablet_num,
-                 last_heartbeat, err_msg, heartbeat_port):
+                 last_heartbeat, err_msg):
         self.id = id
         self.backend_id = backend_id
         self.decommissioned = decommissioned
@@ -49,7 +46,6 @@ class BEState(object):
         self.tablet_num = tablet_num
         self.last_heartbeat = last_heartbeat
         self.err_msg = err_msg
-        self.heartbeat_port = heartbeat_port
 
 
 class DBManager(object):
@@ -57,11 +53,8 @@ class DBManager(object):
     def __init__(self):
         self.fe_states = {}
         self.be_states = {}
-        self.query_port = -1
         self.conn = None
-
-    def set_query_port(self, query_port):
-        self.query_port = query_port
+        self.master_fe_ip = ""
 
     def get_fe(self, id):
         return self.fe_states.get(id, None)
@@ -69,8 +62,8 @@ class DBManager(object):
     def get_be(self, id):
         return self.be_states.get(id, None)
 
-    def load_states(self, query_ports):
-        self._load_fe_states(query_ports)
+    def load_states(self):
+        self._load_fe_states()
         self._load_be_states()
 
     def add_fe(self, fe_endpoint):
@@ -189,108 +182,96 @@ class DBManager(object):
             LOG.error(f"Failed to create default storage vault: {str(e)}")
             raise
 
-    def _load_fe_states(self, query_ports):
+    def _load_fe_states(self):
         fe_states = {}
-        alive_master_fe_port = None
-        for record in self._exec_query('''
-            show frontends '''):
-            # Unpack the record into individual columns
-            name, ip, edit_log_port, _, query_port, _, _, role, is_master, 
cluster_id, _, alive, _, _, last_heartbeat, _, err_msg, _, _ = record
-            is_master = utils.is_true(is_master)
-            alive = utils.is_true(alive)
+        alive_master_fe_ip = None
+        for record in self._exec_query("show frontends"):
+            name = record["Name"]
+            ip = record["Host"]
+            role = record["Role"]
+            is_master = utils.is_true(record["IsMaster"])
+            alive = utils.is_true(record["Alive"])
             id = CLUSTER.Node.get_id_from_ip(ip)
-            query_port = query_ports.get(id, "")
-            last_heartbeat = utils.escape_null(last_heartbeat)
-            fe = FEState(id, query_port, is_master, alive, last_heartbeat,
-                         err_msg, edit_log_port)
+            last_heartbeat = utils.escape_null(record["LastHeartbeat"])
+            err_msg = record["ErrMsg"]
+            fe = FEState(id, is_master, alive, last_heartbeat, err_msg)
             fe_states[id] = fe
-            if is_master and alive and query_port:
-                alive_master_fe_port = query_port
-            LOG.info(
+            if is_master and alive:
+                alive_master_fe_ip = ip
+            LOG.debug(
                 "record of show frontends, name {}, ip {}, alive {}, is_master 
{}, role {}"
                 .format(name, ip, alive, is_master, role))
 
         self.fe_states = fe_states
-        if alive_master_fe_port and alive_master_fe_port != self.query_port:
-            self.query_port = alive_master_fe_port
+        if alive_master_fe_ip and alive_master_fe_ip != self.master_fe_ip:
+            self.master_fe_ip = alive_master_fe_ip
             self._reset_conn()
 
     def _load_be_states(self):
         be_states = {}
-        for record in self._exec_query('''
-            select BackendId, Host, LastHeartbeat, Alive, 
SystemDecommissioned, TabletNum, ErrMsg, HeartbeatPort
-            from backends()'''):
-            backend_id, ip, last_heartbeat, alive, decommissioned, tablet_num, 
err_msg, heartbeat_port = record
-            backend_id = int(backend_id)
-            alive = utils.is_true(alive)
-            decommissioned = utils.is_true(decommissioned)
-            tablet_num = int(tablet_num)
-            id = CLUSTER.Node.get_id_from_ip(ip)
-            last_heartbeat = utils.escape_null(last_heartbeat)
-            heartbeat_port = utils.escape_null(heartbeat_port)
+        for record in self._exec_query("show backends"):
+            backend_id = int(record["BackendId"])
+            alive = utils.is_true(record["Alive"])
+            decommissioned = utils.is_true(record["SystemDecommissioned"])
+            tablet_num = int(record["TabletNum"])
+            id = CLUSTER.Node.get_id_from_ip(record["Host"])
+            last_heartbeat = utils.escape_null(record["LastHeartbeat"])
+            err_msg = record["ErrMsg"]
             be = BEState(id, backend_id, decommissioned, alive, tablet_num,
-                         last_heartbeat, err_msg, heartbeat_port)
+                         last_heartbeat, err_msg)
             be_states[id] = be
         self.be_states = be_states
 
+    # return rows, and each row is a record map
     def _exec_query(self, sql):
         self._prepare_conn()
         with self.conn.cursor() as cursor:
             cursor.execute(sql)
-            return cursor.fetchall()
+            fields = [field_md[0] for field_md in cursor.description
+                      ] if cursor.description else []
+            return [dict(zip(fields, row)) for row in cursor.fetchall()]
 
     def _prepare_conn(self):
         if self.conn:
             return
-        if self.query_port <= 0:
-            raise Exception("Not set query_port")
         self._reset_conn()
 
     def _reset_conn(self):
         self.conn = pymysql.connect(user="root",
-                                    host="127.0.0.1",
+                                    host=self.master_fe_ip,
                                     read_timeout=10,
-                                    port=self.query_port)
+                                    connect_timeout=3,
+                                    port=CLUSTER.FE_QUERY_PORT)
 
 
 def get_db_mgr(cluster_name, required_load_succ=True):
     assert cluster_name
     db_mgr = DBManager()
-    containers = utils.get_doris_containers(cluster_name).get(
-        cluster_name, None)
-    if not containers:
+    master_fe_ip_file = os.path.join(CLUSTER.get_status_path(cluster_name),
+                                     "master_fe_ip")
+    master_fe_ip = None
+    if os.path.exists(master_fe_ip_file):
+        with open(master_fe_ip_file, "r") as f:
+            master_fe_ip = f.read().strip()
+
+    if not master_fe_ip:
         return db_mgr
-    alive_fe_ports = {}
+
+    has_alive_fe = False
+    containers = utils.get_doris_containers(cluster_name).get(cluster_name, [])
     for container in containers:
         if utils.is_container_running(container):
-            _, node_type, id = utils.parse_service_name(container.name)
+            _, node_type, _ = utils.parse_service_name(container.name)
             if node_type == CLUSTER.Node.TYPE_FE:
-                query_port = utils.get_map_ports(container).get(
-                    CLUSTER.FE_QUERY_PORT, None)
-                if query_port:
-                    alive_fe_ports[id] = query_port
-    if not alive_fe_ports:
+                has_alive_fe = True
+                break
+
+    if not has_alive_fe:
         return db_mgr
 
-    master_fe_ip_file = os.path.join(CLUSTER.get_status_path(cluster_name),
-                                     "master_fe_ip")
-    query_port = None
-    if os.path.exists(master_fe_ip_file):
-        with open(master_fe_ip_file, "r") as f:
-            master_fe_ip = f.read()
-            if master_fe_ip:
-                master_id = CLUSTER.Node.get_id_from_ip(master_fe_ip)
-                query_port = alive_fe_ports.get(master_id, None)
-    if not query_port:
-        # A new cluster's master is fe-1
-        if 1 in alive_fe_ports:
-            query_port = alive_fe_ports[1]
-        else:
-            query_port = list(alive_fe_ports.values())[0]
-
-    db_mgr.set_query_port(query_port)
+    db_mgr.master_fe_ip = master_fe_ip
     try:
-        db_mgr.load_states(alive_fe_ports)
+        db_mgr.load_states()
     except Exception as e:
         if required_load_succ:
             raise e
diff --git a/docker/runtime/doris-compose/doris-compose.py 
b/docker/runtime/doris-compose/doris-compose.py
index 0091b70eae9..a2d3a517553 100644
--- a/docker/runtime/doris-compose/doris-compose.py
+++ b/docker/runtime/doris-compose/doris-compose.py
@@ -45,6 +45,9 @@ def run(args, disable_log, help):
 
 if __name__ == '__main__':
     args, help = parse_args()
+    verbose = getattr(args, "verbose", False)
+    if verbose:
+        utils.set_log_verbose()
     disable_log = getattr(args, "output_json", False)
     if disable_log:
         utils.set_enable_log(False)
@@ -53,13 +56,13 @@ if __name__ == '__main__':
     try:
         data = run(args, disable_log, help)
         if disable_log:
-            print(utils.pretty_json({"code": 0, "data": data}))
+            print(utils.pretty_json({"code": 0, "data": data}), flush=True)
         code = 0
     except:
         err = traceback.format_exc()
         if disable_log:
-            print(utils.pretty_json({"code": 1, "err": err}))
+            print(utils.pretty_json({"code": 1, "err": err}), flush=True)
         else:
-            print(err)
+            print(err, flush=True)
         code = 1
     sys.exit(code)
diff --git a/docker/runtime/doris-compose/requirements.txt 
b/docker/runtime/doris-compose/format-code.sh
similarity index 89%
copy from docker/runtime/doris-compose/requirements.txt
copy to docker/runtime/doris-compose/format-code.sh
index 05258de2df6..0626662e641 100644
--- a/docker/runtime/doris-compose/requirements.txt
+++ b/docker/runtime/doris-compose/format-code.sh
@@ -15,11 +15,5 @@
 # specific language governing permissions and limitations
 # under the License.
 
-docker
-docker-compose
-filelock
-jsonpickle
-prettytable
-pymysql
-python-dateutil
-requests<=2.31.0
+yapf -i *.py
+shfmt -w resource/*.sh
diff --git a/docker/runtime/doris-compose/requirements.txt 
b/docker/runtime/doris-compose/requirements.txt
index 05258de2df6..2f962ed68d8 100644
--- a/docker/runtime/doris-compose/requirements.txt
+++ b/docker/runtime/doris-compose/requirements.txt
@@ -22,4 +22,5 @@ jsonpickle
 prettytable
 pymysql
 python-dateutil
+#pyyaml==5.4.1
 requests<=2.31.0
diff --git a/docker/runtime/doris-compose/resource/common.sh 
b/docker/runtime/doris-compose/resource/common.sh
index a1c1b3ff2a5..40833d01dc6 100644
--- a/docker/runtime/doris-compose/resource/common.sh
+++ b/docker/runtime/doris-compose/resource/common.sh
@@ -120,10 +120,11 @@ wait_pid() {
     health_log ""
     health_log "ps -elf\n$(ps -elf)\n"
     if [ -z $pid ]; then
-        health_log "pid not exist"
+        health_log "pid $pid not exist"
         exit 1
     fi
 
+    health_log "pid $pid exist"
     health_log "wait process $pid"
     while true; do
         ps -p $pid >/dev/null
@@ -132,5 +133,13 @@ wait_pid() {
         fi
         sleep 1s
     done
+
+    health_log "show dmesg -T: "
+    dmesg -T | tail -n 50 | tee -a $LOG_FILE
+
+    health_log "show ps -elf"
+    health_log "ps -elf\n$(ps -elf)\n"
+    health_log "pid $pid not exist"
+
     health_log "wait end"
 }
diff --git a/docker/runtime/doris-compose/resource/init_fe.sh 
b/docker/runtime/doris-compose/resource/init_fe.sh
index d4aad29b0e5..39d3ed3fa93 100755
--- a/docker/runtime/doris-compose/resource/init_fe.sh
+++ b/docker/runtime/doris-compose/resource/init_fe.sh
@@ -45,8 +45,8 @@ fe_daemon() {
         sleep 1
         output=$(mysql -P $FE_QUERY_PORT -h $MY_IP -u root --execute "SHOW 
FRONTENDS;")
         code=$?
-        health_log "$output"
         if [ $code -ne 0 ]; then
+            health_log "exec show frontends bad: $output"
             continue
         fi
         header=$(grep IsMaster <<<$output)
diff --git a/docker/runtime/doris-compose/utils.py 
b/docker/runtime/doris-compose/utils.py
index 01dfb64fe42..735947e86bd 100644
--- a/docker/runtime/doris-compose/utils.py
+++ b/docker/runtime/doris-compose/utils.py
@@ -56,6 +56,10 @@ def is_enable_log():
     return ENABLE_LOG
 
 
+def set_log_verbose():
+    get_logger().setLevel(logging.DEBUG)
+
+
 def get_logger(name=None):
     global LOG
     if LOG != None:
diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
index 028bcc71877..5e79ccef21d 100644
--- 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy
@@ -45,6 +45,7 @@ class Config {
     public String jdbcUrl
     public String jdbcUser
     public String jdbcPassword
+
     public String defaultDb
 
     public String ccrDownstreamUrl
@@ -70,7 +71,7 @@ class Config {
     public String metaServiceHttpAddress
     public String recycleServiceHttpAddress
 
-    public RunMode isCloudMode = RunMode.UNKNOWN
+    public RunMode runMode = RunMode.UNKNOWN
 
     public String suitePath
     public String dataPath
@@ -300,6 +301,20 @@ class Config {
         config.dorisComposePath = 
FileUtils.getCanonicalPath(config.dorisComposePath)
         config.image = cmd.getOptionValue(imageOpt, config.image)
         config.dockerEndNoKill = cmd.hasOption(noKillDockerOpt)
+        if (cmd.hasOption(runModeOpt)) {
+            String runMode = cmd.getOptionValue(runModeOpt, "unknown")
+            if (runMode.equalsIgnoreCase("unknown")) {
+                config.runMode = RunMode.UNKNOWN;
+            } else if (runMode.equalsIgnoreCase("cloud")) {
+                config.runMode = RunMode.CLOUD;
+            } else if (runMode.equalsIgnoreCase("not_cloud")) {
+                config.runMode = RunMode.NOT_CLOUD;
+            } else {
+                throw new IllegalStateException("Bad runMode: ${runMode}, 
should be one of unknown/cloud/not_cloud, "
+                        + "if is unknown, fetch it from fe")
+            }
+        }
+        log.info("runMode: ${config.runMode}")
         config.suiteWildcard = cmd.getOptionValue(suiteOpt, config.testSuites)
                 .split(",")
                 .collect({s -> s.trim()})
@@ -500,8 +515,8 @@ class Config {
         Properties props = cmd.getOptionProperties("conf")
         config.otherConfigs.putAll(props)
 
-        config.tryCreateDbIfNotExist()
-        config.buildUrlWithDefaultDb()
+        // mainly auth_xxx cases use defaultDb, these suites better not use 
defaultDb
+        config.createDefaultDb()
 
         return config
     }
@@ -922,7 +937,25 @@ class Config {
         return null
     }
 
-    void tryCreateDbIfNotExist(String dbName = defaultDb) {
+    void createDefaultDb() {
+        String dbName = null
+        try {
+            tryCreateDbIfNotExist(defaultDb)
+            dbName = defaultDb
+        } catch (Exception e) {
+            // defaultDb is not need for most cases.
+            // when run docker suites without external fe/be,  createDefaultDb 
will fail, but can ignore this exception.
+            // Infact, only mainly auth_xxx cases use defaultDb, and they just 
use jdbcUrl in connect function.
+            // And they can avoid using defaultDb too. But modify all these 
cases take a lot work.
+            // We better delete all the usage of defaultDb in suites later, 
and all suites should use their own db, not the defaultDb.
+            log.warn("create default db failed ${defaultDb}".toString())
+        }
+
+        jdbcUrl = buildUrlWithDb(jdbcUrl, dbName)
+        log.info("Reset jdbcUrl to ${jdbcUrl}".toString())
+    }
+
+    void tryCreateDbIfNotExist(String dbName) {
         // connect without specify default db
         try {
             String sql = "CREATE DATABASE IF NOT EXISTS ${dbName}"
@@ -952,17 +985,20 @@ class Config {
         }
     }
 
-    boolean fetchRunMode() {
-        if (isCloudMode == RunMode.UNKNOWN) {
+    boolean isCloudMode() {
+        fetchCloudMode()
+        return runMode == RunMode.CLOUD
+    }
+
+    void fetchCloudMode() {
+        if (runMode == RunMode.UNKNOWN) {
             try {
                 def result = JdbcUtils.executeToMapArray(getRootConnection(), 
"SHOW FRONTEND CONFIG LIKE 'cloud_unique_id'")
-                isCloudMode = result[0].Value.toString().isEmpty() ? 
RunMode.NOT_CLOUD : RunMode.CLOUD
+                runMode = result[0].Value.toString().isEmpty() ? 
RunMode.NOT_CLOUD : RunMode.CLOUD
             } catch (Throwable t) {
                 throw new IllegalStateException("Fetch server config 
'cloud_unique_id' failed, jdbcUrl: ${jdbcUrl}", t)
             }
         }
-        return isCloudMode == RunMode.CLOUD
-
     }
 
     Connection getConnection() {
@@ -974,12 +1010,16 @@ class Config {
     }
 
     Connection getConnectionByDbName(String dbName) {
-        String dbUrl = buildUrlWithDb(jdbcUrl, dbName)
+        String dbUrl = getConnectionUrlByDbName(dbName)
         tryCreateDbIfNotExist(dbName)
         log.info("connect to ${dbUrl}".toString())
         return DriverManager.getConnection(dbUrl, jdbcUser, jdbcPassword)
     }
 
+    String getConnectionUrlByDbName(String dbName) {
+        return buildUrlWithDb(jdbcUrl, dbName)
+    }
+
     Connection getConnectionByArrowFlightSql(String dbName) {
         Class.forName("org.apache.arrow.driver.jdbc.ArrowFlightJdbcDriver")
         String arrowFlightSqlHost = otherConfigs.get("extArrowFlightSqlHost")
@@ -1056,12 +1096,11 @@ class Config {
         }
     }
 
-    public void buildUrlWithDefaultDb() {
-        this.jdbcUrl = buildUrlWithDb(jdbcUrl, defaultDb)
-        log.info("Reset jdbcUrl to ${jdbcUrl}".toString())
-    }
-
     public static String buildUrlWithDbImpl(String jdbcUrl, String dbName) {
+        if (!dbName?.trim()) {
+            return jdbcUrl
+        }
+
         String urlWithDb = jdbcUrl
         String urlWithoutSchema = jdbcUrl.substring(jdbcUrl.indexOf("://") + 3)
         if (urlWithoutSchema.indexOf("/") >= 0) {
diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/ConfigOptions.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/ConfigOptions.groovy
index 5b220949168..a648eb40a3e 100644
--- 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/ConfigOptions.groovy
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/ConfigOptions.groovy
@@ -56,6 +56,7 @@ class ConfigOptions {
     static Option sslCertificateOpt
     static Option imageOpt
     static Option noKillDockerOpt
+    static Option runModeOpt
     static Option suiteOpt
     static Option excludeSuiteOpt
     static Option groupsOpt
@@ -218,6 +219,14 @@ class ConfigOptions {
                 .desc("don't kill docker containers")
                 .build()
 
+        runModeOpt = Option.builder("runMode")
+                .required(false)
+                .hasArg(true)
+                .type(String.class)
+                .longOpt("runMode")
+                .desc("specific run mode: unknown/cloud/not_cloud. if unknow, 
will fetch it from fe.")
+                .build()
+
         suiteOpt = Option.builder("s")
                 .argName("suiteName")
                 .required(false)
@@ -597,6 +606,7 @@ class ConfigOptions {
                 .addOption(sslCertificateOpt)
                 .addOption(imageOpt)
                 .addOption(noKillDockerOpt)
+                .addOption(runModeOpt)
                 .addOption(confOpt)
                 .addOption(suiteOpt)
                 .addOption(excludeSuiteOpt)
diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/ProfileAction.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/ProfileAction.groovy
index b019e6c24aa..5f6c00be943 100644
--- 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/ProfileAction.groovy
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/action/ProfileAction.groovy
@@ -70,7 +70,7 @@ class ProfileAction implements SuiteAction {
             httpCli.op("get")
             httpCli.printResponse(false)
 
-            if (context.config.fetchRunMode()) {
+            if (context.config.isCloudMode()) {
                 httpCli.basicAuthorization(context.config.feCloudHttpUser, 
context.config.feCloudHttpPassword)
             } else {
                 httpCli.basicAuthorization(context.config.feHttpUser, 
context.config.feHttpPassword)
@@ -92,7 +92,7 @@ class ProfileAction implements SuiteAction {
                         profileCli.op("get")
                         profileCli.printResponse(false)
 
-                        if (context.config.fetchRunMode()) {
+                        if (context.config.isCloudMode()) {
                             
profileCli.basicAuthorization(context.config.feCloudHttpUser, 
context.config.feCloudHttpPassword)
                         } else {
                             
profileCli.basicAuthorization(context.config.feHttpUser, 
context.config.feHttpPassword)
diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy
index f5816ab762d..fb0743eceed 100644
--- 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy
@@ -279,6 +279,7 @@ class Suite implements GroovyInterceptable {
         )
     }
 
+    // more explaination can see example file: demo_p0/docker_action.groovy
     public void docker(ClusterOptions options = new ClusterOptions(), Closure 
actionSupplier) throws Exception {
         if (context.config.excludeDockerTest) {
             return
@@ -289,15 +290,25 @@ class Suite implements GroovyInterceptable {
                     + "see example demo_p0/docker_action.groovy")
         }
 
-        boolean pipelineIsCloud = isCloudMode()
+        try {
+            context.config.fetchCloudMode()
+        } catch (Exception e) {
+        }
+
         boolean dockerIsCloud = false
         if (options.cloudMode == null) {
-            dockerIsCloud = pipelineIsCloud
+            if (context.config.runMode == RunMode.UNKNOWN) {
+                throw new Exception("Bad run mode, cloud or not_cloud is 
unknown")
+            }
+            dockerIsCloud = context.config.runMode == RunMode.CLOUD
         } else {
-            dockerIsCloud = options.cloudMode
-            if (dockerIsCloud != pipelineIsCloud && 
options.skipRunWhenPipelineDiff) {
+            if (options.cloudMode == true && context.config.runMode == 
RunMode.NOT_CLOUD) {
                 return
             }
+            if (options.cloudMode == false && context.config.runMode == 
RunMode.CLOUD) {
+                return
+            }
+            dockerIsCloud = options.cloudMode
         }
 
         try {
@@ -558,6 +569,14 @@ class Suite implements GroovyInterceptable {
         }
     }
 
+    String getCurDbName() {
+        return context.dbName
+    }
+
+    String getCurDbConnectUrl() {
+        return context.config.getConnectionUrlByDbName(getCurDbName())
+    }
+
     long getDbId() {
         def dbInfo = sql "show proc '/dbs'"
         for(List<Object> row : dbInfo) {
@@ -1459,7 +1478,7 @@ class Suite implements GroovyInterceptable {
     }
 
     boolean isCloudMode() {
-        return context.config.fetchRunMode()
+        return context.config.isCloudMode()
     }
 
     boolean enableStoragevault() {
diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/SuiteCluster.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/SuiteCluster.groovy
index 862f437840e..33dfac54d8b 100644
--- 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/SuiteCluster.groovy
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/SuiteCluster.groovy
@@ -66,10 +66,6 @@ class ClusterOptions {
     // default use 1
     Boolean useFollowersMode = false
 
-    // when cloudMode = true/false,  but the running pipeline is diff with 
cloudMode,
-    // skip run this docker test or not.
-    boolean skipRunWhenPipelineDiff = true
-
     // each be disks, a disks format is: disk_type=disk_num[,disk_capacity]
     // here disk_type=HDD or SSD,  disk capacity is in gb unit.
     // for example: beDisks = ["HDD=1", "SSD=2,10", "SSD=10,3"] means:
diff --git a/regression-test/suites/demo_p0/docker_action.groovy 
b/regression-test/suites/demo_p0/docker_action.groovy
index bfe9c0039e2..d13c5d13e54 100644
--- a/regression-test/suites/demo_p0/docker_action.groovy
+++ b/regression-test/suites/demo_p0/docker_action.groovy
@@ -17,13 +17,30 @@
 
 import org.apache.doris.regression.suite.ClusterOptions
 
-// run docker suite steps:
+// Run docker suite steps:
 // 1. Read 'docker/runtime/doris-compose/Readme.md', make sure you can setup a 
doris docker cluster;
 // 2. update regression-conf-custom.groovy with config:
 //    image = "xxxx"                // your doris docker image
-//    excludeDockerTest = false     // do run docker suite, default is false
+//    excludeDockerTest = false     // do run docker suite, default is true
 //    dockerEndDeleteFiles = false  // after run docker suite, whether delete 
contains's log and data in directory '/tmp/doris/<suite-name>'
 
+// When run docker suite, then no need an external doris cluster.
+// But whether run a docker suite, need more check.
+// Firstly, get the pipeline's run mode (cloud or not_cloud):
+// If there's an external doris cluster, then fetch pipeline's runMode from it.
+// If there's no external doris cluster, then set pipeline's runMode with 
command args.
+//    for example:  sh run-regression-test.sh --run docker_action  
-runMode=cloud/not_cloud
+// Secondly, compare ClusterOptions.cloudMode and pipeline's runMode
+// If ClusterOptions.cloudMode = null then let ClusterOptions.cloudMode = 
pipeline's cloudMode, and run docker suite.
+// if ClusterOptions.cloudMode = true or false, if cloudMode == pipeline's 
cloudMode or pipeline's cloudMode is unknown,
+//      then run docker suite, otherwise don't run docker suite.
+
+// NOTICE:
+// 1. No need to use code ` if (isCloudMode()) { return } `  in docker suites,
+// instead should use `ClusterOptions.cloudMode = true/false` is enough.
+// Because when run docker suite without an external doris cluster, if suite 
use code `isCloudMode()`, it need specific -runMode=cloud/not_cloud.
+// On the contrary, `ClusterOptions.cloudMode = true/false` no need specific 
-runMode=cloud/not_cloud when no external doris cluster exists.
+
 // need add 'docker' to suite's group, and don't add 'nonConcurrent' to it
 suite('docker_action', 'docker') {
     // run a new docker
@@ -48,7 +65,7 @@ suite('docker_action', 'docker') {
 
     def options = new ClusterOptions()
     // add fe config items
-    options.feConfigs = ['example_conf_k1=v1', 'example_conf_k2=v2']
+    options.feConfigs += ['example_conf_k1=v1', 'example_conf_k2=v2']
     // contains 5 backends
     options.beNum = 5
     // each backend has 1 HDD disk and 3 SSD disks
@@ -63,8 +80,6 @@ suite('docker_action', 'docker') {
     options2.beNum = 1
     // create cloud cluster
     options2.cloudMode = true
-    //// cloud docker only run in cloud pipeline, but enable it run in 
none-cloud pipeline
-    // options2.skipRunWhenPipelineDiff = false
     // run another docker, create a cloud cluster
     docker(options2) {
         // cloud cluster will ignore replication_num, always set to 1. so 
create table succ even has 1 be.
diff --git 
a/regression-test/suites/schema_change_p0/test_abort_txn_by_be_local5.groovy 
b/regression-test/suites/schema_change_p0/test_abort_txn_by_be_local5.groovy
index 0df8254ff25..3835da4ccb2 100644
--- a/regression-test/suites/schema_change_p0/test_abort_txn_by_be_local5.groovy
+++ b/regression-test/suites/schema_change_p0/test_abort_txn_by_be_local5.groovy
@@ -21,7 +21,6 @@ import org.apache.http.NoHttpResponseException
 suite('test_abort_txn_by_be_local5', 'docker') {
     def options = new ClusterOptions()
     options.cloudMode = false
-    options.skipRunWhenPipelineDiff = false
     options.enableDebugPoints()
     options.beConfigs += [ "enable_java_support=false" ]
     options.feConfigs += [ "enable_abort_txn_by_checking_coordinator_be=true" ]
diff --git 
a/regression-test/suites/schema_change_p0/test_abort_txn_by_be_local6.groovy 
b/regression-test/suites/schema_change_p0/test_abort_txn_by_be_local6.groovy
index a95d335579b..ff53c412590 100644
--- a/regression-test/suites/schema_change_p0/test_abort_txn_by_be_local6.groovy
+++ b/regression-test/suites/schema_change_p0/test_abort_txn_by_be_local6.groovy
@@ -21,7 +21,6 @@ import org.apache.http.NoHttpResponseException
 suite('test_abort_txn_by_be_local6', 'docker') {
     def options = new ClusterOptions()
     options.cloudMode = false
-    options.skipRunWhenPipelineDiff = true
     options.enableDebugPoints()
     options.beConfigs += [ "enable_java_support=false" ]
     options.feConfigs += [ "enable_abort_txn_by_checking_coordinator_be=false" 
]
diff --git 
a/regression-test/suites/schema_change_p0/test_abort_txn_by_fe_local3.groovy 
b/regression-test/suites/schema_change_p0/test_abort_txn_by_fe_local3.groovy
index 355dab05879..32cd9d0eba7 100644
--- a/regression-test/suites/schema_change_p0/test_abort_txn_by_fe_local3.groovy
+++ b/regression-test/suites/schema_change_p0/test_abort_txn_by_fe_local3.groovy
@@ -21,7 +21,6 @@ import org.apache.http.NoHttpResponseException
 suite('test_abort_txn_by_fe_local3', 'docker') {
     def options = new ClusterOptions()
     options.cloudMode = false
-    options.skipRunWhenPipelineDiff = false
     options.enableDebugPoints()
     options.beConfigs += [ "enable_java_support=false" ]
     options.feConfigs += [ "enable_abort_txn_by_checking_coordinator_be=false" 
]
diff --git a/run-regression-test.sh b/run-regression-test.sh
index 6357f4111a7..16256152887 100755
--- a/run-regression-test.sh
+++ b/run-regression-test.sh
@@ -46,6 +46,8 @@ Usage: $0 <shell_options> <framework_options>
      -dockerSuiteParallel              run docker tests using specified threads
      -randomOrder                      run tests in a random order
      -noKillDocker                     don't kill container when finish docker 
suites
+     -runMode                          if run docker suites, no need to setup 
external doris clusters.
+                                       user may specify run mode: cloud or 
not_cloud.
      -times                            rum tests {times} times
 
   Eg.


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org


Reply via email to