mirror of
https://github.com/taosdata/TDengine
synced 2026-05-24 10:09:01 +00:00
477 lines
13 KiB
Python
477 lines
13 KiB
Python
"""
|
|
XNode 生命周期功能测试
|
|
|
|
测试 XNode 节点的创建、状态转换、心跳机制、删除等完整生命周期
|
|
"""
|
|
|
|
import random
|
|
import time
|
|
import uuid
|
|
|
|
from new_test_framework.utils import tdLog, tdSql
|
|
|
|
|
|
class TestXnodeLifecycle:
|
|
"""XNode 生命周期测试"""
|
|
|
|
replicaVar = 1
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
tdLog.debug(f"start to execute {__file__}")
|
|
cls.suffix = uuid.uuid4().hex[:6]
|
|
tdSql.prepare(
|
|
dbname=f"xnode_lifecycle_db_{cls.suffix}", drop=True, replica=cls.replicaVar
|
|
)
|
|
|
|
def test_create_xnode_with_user_password(self):
|
|
"""测试创建第一个 XNode 时需要用户密码
|
|
|
|
1. Create xnode with user password
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
ep1 = f"xn-life-{self.suffix}-1:6050"
|
|
user = f"__xnode_test_{self.suffix}__"
|
|
password = f"Xn0de!{self.suffix}"
|
|
|
|
# 第一次创建 XNode 需要指定用户和密码
|
|
sql = f"CREATE XNODE '{ep1}' USER {user} PASS '{password}'"
|
|
tdLog.info(f"Creating first xnode with user/password: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
tdLog.success(f"Successfully created xnode with user {user}")
|
|
except Exception as e:
|
|
# 可能因为 xnoded 进程未运行而失败,但语法应该正确
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg, \
|
|
f"Syntax error creating xnode: {e}"
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
# 查询 XNODES 系统表
|
|
try:
|
|
tdSql.query("SHOW XNODES")
|
|
tdLog.success("Successfully queried SHOW XNODES")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
def test_create_xnode_without_password(self):
|
|
"""测试创建第二个 XNode 时不需要密码
|
|
|
|
1. Create xnode without user password
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
ep2 = f"xn-life-{self.suffix}-2:6051"
|
|
|
|
# 第二次创建 XNode 不需要用户密码
|
|
sql = f"CREATE XNODE '{ep2}'"
|
|
tdLog.info(f"Creating second xnode without password: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
tdLog.success(f"Successfully created xnode {ep2}")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
def test_create_duplicate_xnode(self):
|
|
"""测试创建重复的 XNode 应该失败
|
|
|
|
1. Create duplicate xnode
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
ep = f"xn-life-{self.suffix}-dup:6052"
|
|
|
|
# 第一次创建
|
|
try:
|
|
tdSql.execute(f"DROP XNODE FORCE '{ep}'", queryTimes=1)
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
tdSql.execute(f"CREATE XNODE '{ep}'", queryTimes=1)
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
|
|
# 第二次创建同一个 - 应该失败(运行时错误)
|
|
try:
|
|
tdSql.execute(f"CREATE XNODE '{ep}'", queryTimes=1)
|
|
tdLog.notice("Duplicate xnode creation did not fail as expected")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.success(f"Duplicate xnode correctly rejected: {e}")
|
|
|
|
def test_drop_xnode_by_endpoint(self):
|
|
"""测试通过 endpoint 删除 XNode
|
|
|
|
1. Drop xnode by endpoint
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
ep = f"xn-life-{self.suffix}-drop1:6053"
|
|
|
|
# 先创建
|
|
try:
|
|
tdSql.execute(f"CREATE XNODE '{ep}'", queryTimes=1)
|
|
except:
|
|
pass
|
|
|
|
# 删除
|
|
sql = f"DROP XNODE '{ep}'"
|
|
tdLog.info(f"Dropping xnode by endpoint: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
tdLog.success(f"Successfully dropped xnode {ep}")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
def test_drop_xnode_by_id(self):
|
|
"""测试通过 ID 删除 XNode
|
|
|
|
1. Drop xnode by ID
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
xnode_id = random.randint(1000, 9999)
|
|
|
|
sql = f"DROP XNODE {xnode_id}"
|
|
tdLog.info(f"Dropping xnode by ID: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
def test_drop_xnode_force(self):
|
|
"""测试强制删除 XNode
|
|
|
|
1. Drop xnode force
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
ep = f"xn-life-{self.suffix}-force:6054"
|
|
|
|
# 先创建
|
|
try:
|
|
tdSql.execute(f"CREATE XNODE '{ep}'", queryTimes=1)
|
|
except:
|
|
pass
|
|
|
|
# 强制删除
|
|
sql = f"DROP XNODE FORCE '{ep}'"
|
|
tdLog.info(f"Force dropping xnode: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
tdLog.success(f"Successfully force dropped xnode {ep}")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
def test_drain_xnode(self):
|
|
"""测试 DRAIN XNode 模式
|
|
|
|
1. Drain xnode
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
xnode_id = random.randint(1000, 9999)
|
|
|
|
sql = f"DRAIN XNODE {xnode_id}"
|
|
tdLog.info(f"Draining xnode: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
def test_drop_nonexistent_xnode(self):
|
|
"""测试删除不存在的 XNode
|
|
|
|
1. Drop non-exist xnode
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
ep = f"xn-nonexist-{uuid.uuid4().hex[:8]}:9999"
|
|
|
|
sql = f"DROP XNODE '{ep}'"
|
|
tdLog.info(f"Dropping non-existent xnode: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
# 如果没有报错,说明语法正确
|
|
tdLog.notice(f"No error for dropping non-existent xnode")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
# 应该是 "xnode not exist" 之类的运行时错误
|
|
tdLog.success(f"Correctly got runtime error: {e}")
|
|
|
|
def test_show_xnodes_query(self):
|
|
"""测试 SHOW XNODES 查询
|
|
|
|
1. Query show xnodes
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
sql = "SHOW XNODES"
|
|
tdLog.info(f"Querying: {sql}")
|
|
try:
|
|
tdSql.query(sql)
|
|
rows = tdSql.queryRows
|
|
tdLog.success(f"SHOW XNODES returned {rows} rows")
|
|
|
|
# 如果有结果,检查列
|
|
if rows > 0:
|
|
# 系统表应该包含: id, endpoint, status, create_time, update_time 等
|
|
tdLog.info(f"Sample xnode data: {tdSql.queryResult}")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
def test_xnode_with_special_characters(self):
|
|
"""测试包含特殊字符的 XNode 地址
|
|
|
|
1. Create xnode with special characters
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
# 测试 IPv6 地址格式
|
|
ep_ipv6 = f"[::1]:6055"
|
|
sql = f"CREATE XNODE '{ep_ipv6}'"
|
|
tdLog.info(f"Creating xnode with IPv6: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
tdLog.success(f"Successfully created xnode with IPv6")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
# 清理
|
|
try:
|
|
tdSql.execute(f"DROP XNODE FORCE '{ep_ipv6}'", queryTimes=1)
|
|
except:
|
|
pass
|
|
|
|
def test_xnode_password_validation(self):
|
|
"""测试密码格式验证
|
|
|
|
1. Create xnode with weak password
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
ep = f"xn-pwd-{self.suffix}:6056"
|
|
|
|
# 测试弱密码(如果启用强密码策略)
|
|
weak_pass = "123"
|
|
sql = f"CREATE XNODE '{ep}' USER __xnode__ PASS '{weak_pass}'"
|
|
tdLog.info(f"Testing weak password: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
tdLog.notice("Weak password was accepted")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
if "password" in msg or "pass" in msg:
|
|
tdLog.success(f"Weak password correctly rejected: {e}")
|
|
else:
|
|
tdLog.notice(f"Runtime error: {e}")
|
|
|
|
# 测试复杂密码
|
|
strong_pass = f"C0mpl3x!Pass{self.suffix}"
|
|
sql = f"CREATE XNODE '{ep}' USER __xnode__ PASS '{strong_pass}'"
|
|
tdLog.info(f"Testing strong password: {sql}")
|
|
try:
|
|
tdSql.execute(sql, queryTimes=1)
|
|
tdLog.success("Strong password accepted")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error tolerated: {e}")
|
|
|
|
def test_xnode_lifecycle_sequence(self):
|
|
"""测试完整的 XNode 生命周期序列
|
|
|
|
1. Create xnode
|
|
2. Query xnode status
|
|
3. Drain xnode
|
|
4. Drop xnode
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
ep = f"xn-seq-{self.suffix}:6057"
|
|
xnode_id = None
|
|
|
|
# 1. 创建
|
|
tdLog.info(f"Step 1: Create xnode {ep}")
|
|
try:
|
|
tdSql.execute(f"CREATE XNODE '{ep}'")
|
|
tdLog.success("Created xnode")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
tdLog.notice(f"Runtime error: {e}")
|
|
|
|
# 2. 查询状态
|
|
tdLog.info(f"Step 2: Query xnode status")
|
|
try:
|
|
tdSql.query("SHOW XNODES")
|
|
# 尝试找到刚创建的节点
|
|
for i in range(tdSql.queryRows):
|
|
if ep in str(tdSql.queryResult[i]):
|
|
tdLog.success(f"Found created xnode: {tdSql.queryResult[i]}")
|
|
break
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
|
|
# 3. Drain 模式
|
|
if xnode_id:
|
|
tdLog.info(f"Step 3: Drain xnode {xnode_id}")
|
|
try:
|
|
tdSql.execute(f"DRAIN XNODE {xnode_id}")
|
|
tdLog.success("Drained xnode")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
|
|
# 4. 删除
|
|
tdLog.info(f"Step 4: Drop xnode {ep}")
|
|
try:
|
|
tdSql.execute(f"DROP XNODE FORCE '{ep}'")
|
|
tdLog.success("Dropped xnode")
|
|
except Exception as e:
|
|
msg = str(e).lower()
|
|
assert "syntax" not in msg and "parse" not in msg
|
|
|
|
tdLog.success(f"Completed full lifecycle test for {ep}")
|
|
|
|
@classmethod
|
|
def teardown_class(cls):
|
|
"""清理测试环境
|
|
|
|
1. Drop xnode
|
|
|
|
Since: v3.4.0.0
|
|
|
|
Labels: common,ci
|
|
|
|
Jira: None
|
|
|
|
History:
|
|
- 2025-12-30 GuiChuan Zhang Created
|
|
"""
|
|
|
|
tdLog.info(f"Cleaning up test environment")
|
|
try:
|
|
tdSql.execute(f"DROP DATABASE IF EXISTS xnode_lifecycle_db_{cls.suffix}")
|
|
except:
|
|
pass
|
|
tdLog.success(f"{__file__} test completed")
|