You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by da...@apache.org on 2025/12/09 22:53:52 UTC

(doris) branch master updated: [feat](test) Add clone instance test for recycler (#58773)

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 92913f3f471 [feat](test) Add clone instance test for recycler (#58773)
92913f3f471 is described below

commit 92913f3f4715f79a90673a153447c0db60a5f0a0
Author: Yixuan Wang <wa...@selectdb.com>
AuthorDate: Wed Dec 10 06:53:44 2025 +0800

    [feat](test) Add clone instance test for recycler (#58773)
---
 .../cloud_p0/conf/regression-conf-custom.groovy    |   1 +
 .../recycler/test_recycler_clone_instance.groovy   | 172 +++++++++++++++++++++
 2 files changed, 173 insertions(+)

diff --git a/regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy b/regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy
index ca79ea9d20a..e826376de5b 100644
--- a/regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy
+++ b/regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy
@@ -68,6 +68,7 @@ excludeSuites = "000_the_start_sentinel_do_not_touch," + // keep this line as th
     "test_recycler_with_txn_label," +
     "test_recycler," +
     "test_recycler_cleanup_snapshot," +
+    "test_recycler_clone_instance," +
     "zzz_the_end_sentinel_do_not_touch" // keep this line as the last line
 
 excludeDirectories = "000_the_start_sentinel_do_not_touch," + // keep this line as the first line
diff --git a/regression-test/suites/cloud_p0/recycler/test_recycler_clone_instance.groovy b/regression-test/suites/cloud_p0/recycler/test_recycler_clone_instance.groovy
new file mode 100644
index 00000000000..f3ac48f56ae
--- /dev/null
+++ b/regression-test/suites/cloud_p0/recycler/test_recycler_clone_instance.groovy
@@ -0,0 +1,172 @@
+// 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.
+
+suite("test_recycler_clone_instance") {
+    def enableMultiVersionStatus = context.config.enableMultiVersionStatus
+    def enableClusterSnapshot = context.config.enableClusterSnapshot
+    def metaServiceHttpAddress = context.config.metaServiceHttpAddress
+    def dbName = "regression_test_cloud_p0_recycler"
+
+    if (!enableClusterSnapshot || !enableMultiVersionStatus) {
+        logger.info("enableClusterSnapshot or enableMultiVersionStatus is not true, skip clone instance")
+        return
+    }
+
+    def get_instance_id = { host ->
+        def url = "http://${host}/MetaService/http/get_instance?token=${token}&instance_id=${instance_id}"
+        def (code, out, err) = curl('GET', url, null, 180)
+        assert code == 0 : "Failed to get multi version status: ${out} ${err}"
+        def json = parseJson(out)
+        assert json["code"] == "OK" : "Get instance failed: ${out} ${err}"
+        if (json["result"].containsKey("instance_id")) {
+            return json["result"]["instance_id"]
+        }
+        return null
+    }
+
+    sql "CREATE DATABASE IF NOT EXISTS ${dbName}"
+
+    def instanceId = get_instance_id(metaServiceHttpAddress)
+    def username = "root"
+
+    logger.info("Querying cluster snapshots")
+    def snapshotResult = sql """ SELECT * FROM information_schema.cluster_snapshots """
+    logger.info("Snapshot result: ${snapshotResult}")
+
+    assert snapshotResult != null && !snapshotResult.isEmpty(), "No snapshots found, cannot run this test"
+    
+    def latestSnapshot = snapshotResult[-1]
+    def snapshotId = latestSnapshot[0]
+    def snapshotLabel = latestSnapshot[9]
+    logger.info("Latest snapshot_id: ${snapshotId}, snapshot_label: ${snapshotLabel}")
+    
+    logger.info("Getting FE information")
+    def feResult = sql """ SHOW FRONTENDS """
+    logger.info("FE result: ${feResult}")
+    
+    def masterFe = null
+    for (def fe : feResult) {
+        if (fe[8] == "true") {
+            masterFe = fe
+            break
+        }
+    }
+
+    assert masterFe != null, "No master FE found"
+    
+    def masterFeHost = masterFe[1]
+    logger.info("Master FE host: ${masterFeHost}")
+    
+    def dorisHome = System.getenv("DORIS_HOME")
+    logger.info("DORIS_HOME: ${dorisHome}")
+
+    assert dorisHome != null, "DORIS_HOME is not set"
+
+    def executeCommand = { String cmd, Boolean mustSuc ->
+        try {
+            logger.info("Execute: ${cmd}")
+            def proc = new ProcessBuilder("/bin/bash", "-c", cmd).redirectErrorStream(true).start()
+            int exitcode = proc.waitFor()
+            def output = proc.text
+            if (exitcode != 0) {
+                logger.info("Exit code: ${exitcode}, output: ${output}")
+                if (mustSuc == true) {
+                    throw new Exception("Command failed with exit code ${exitcode}: ${output}")
+                }
+            }
+            return output
+        } catch (IOException e) {
+            logger.error("Execute timeout: ${e.message}")
+            throw new Exception("Execute timeout: ${e.message}")
+        }
+    }
+
+    logger.info("Getting BE information and stopping all BEs and FEs")
+    def beResult = sql """ SHOW BACKENDS """
+    logger.info("BE result: ${beResult}")
+    
+    def beHosts = []
+    for (def be : beResult) {
+        def beHost = be[1]
+        beHosts.add(beHost)
+    }
+    
+    logger.info("BE hosts: ${beHosts}")
+
+    logger.info("Cleaning storage directory on all BEs")
+    def storagePath = "${dorisHome}/be/storage/*"
+    deleteRemotePathOnAllBE(username, storagePath)
+    logger.info("Cleaned storage directory on all BEs")
+
+    logger.info("Stopping master FE on ${masterFeHost} and cleaning doris-meta directory")
+    def stopFeCmd = "ssh -o StrictHostKeyChecking=no ${username}@${masterFeHost} \"${dorisHome}/fe/bin/stop_fe.sh\""
+    executeCommand(stopFeCmd, true)
+    logger.info("Master FE stopped successfully")
+
+    def cleanMetaCmd = "ssh -o StrictHostKeyChecking=no ${username}@${masterFeHost} \"rm -rf ${dorisHome}/fe/doris-meta/*\""
+    executeCommand(cleanMetaCmd, true)
+    logger.info("Cleaned doris-meta directory")
+
+    for (def beHost : beHosts) {
+        logger.info("Stopping BE on ${beHost}")
+        def stopBeCmd = "ssh -o StrictHostKeyChecking=no ${username}@${beHost} \"${dorisHome}/be/bin/stop_be.sh\""
+        executeCommand(stopBeCmd, true)
+        logger.info("BE on ${beHost} stopped successfully")
+    }
+    
+    logger.info("Creating snapshot_info.json")
+    
+    def newInstanceId = instanceId + "_cloned"
+    def instanceName = "cloned_instance"
+    
+    def snapshotInfoJson = """{"from_instance_id":"${instanceId}","from_snapshot_id":"${snapshotId}","instance_id":"${newInstanceId}","name":"${instanceName}","is_successor":true}"""
+    
+    logger.info("Snapshot info JSON content: ${snapshotInfoJson}")
+    
+    def jsonBase64 = snapshotInfoJson.bytes.encodeBase64().toString()
+    
+    def createJsonCmd = "ssh -o StrictHostKeyChecking=no ${username}@${masterFeHost} \"echo '${jsonBase64}' | base64 -d > ${dorisHome}/fe/snapshot_info.json\""
+    executeCommand(createJsonCmd, true)
+    logger.info("Created snapshot_info.json successfully")
+    
+    def verifyJsonCmd = "ssh -o StrictHostKeyChecking=no ${username}@${masterFeHost} \"cat ${dorisHome}/fe/snapshot_info.json\""
+    def jsonContent = executeCommand(verifyJsonCmd, true)
+    logger.info("Verified snapshot_info.json content: ${jsonContent}")
+
+    logger.info("Starting FE with snapshot_info.json")
+    def startFeCmd = "ssh -o StrictHostKeyChecking=no ${username}@${masterFeHost} \"cd ${dorisHome}/fe/bin && ./start_fe.sh --daemon --cluster_snapshot snapshot_info.json\""
+    executeCommand(startFeCmd, true)
+    logger.info("Master FE started successfully with cluster snapshot")
+    
+    logger.info("Waiting for FE to be ready...")
+    Thread.sleep(60000)
+
+    context.reconnectFe()
+    
+    logger.info("Starting all BEs")
+    for (def beHost : beHosts) {
+        logger.info("Starting BE on ${beHost}")
+        def startBeCmd = "ssh -o StrictHostKeyChecking=no ${username}@${beHost} \"cd ${dorisHome}/be/bin && ./start_be.sh --daemon\""
+        executeCommand(startBeCmd, true)
+        logger.info("BE on ${beHost} started successfully")
+    }
+    
+    logger.info("Waiting for BEs to be ready...")
+    Thread.sleep(60000)
+    
+    logger.info("All steps completed successfully. FE and BEs have been restarted with cloned instance.")
+}


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