dts-dance 0.2.5__py3-none-any.whl → 0.2.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dts-dance
3
- Version: 0.2.5
3
+ Version: 0.2.8
4
4
  Summary: dts dance lib
5
5
  Keywords: observation,tools
6
6
  Requires-Python: >=3.12
7
- Requires-Dist: boto3>=1.42.30
7
+ Requires-Dist: boto3>=1.42.35
8
8
  Requires-Dist: loguru<0.8.0,>=0.7.3
9
9
  Requires-Dist: pyyaml<7.0.0,>=6.0.3
10
10
  Requires-Dist: requests-toolbelt==1.0.0
@@ -0,0 +1,16 @@
1
+ dtsdance/__init__.py,sha256=Yl_jEZ5weYfcrklnDvwB4wSgCOvMBLRRgWx0gHs3qfM,49
2
+ dtsdance/bytecloud.py,sha256=LZ-uw5NMmapyNpt89bMKhCgv4U2cGR-XjLzFFFEXGxM,5899
3
+ dtsdance/ddutil.py,sha256=1ZUmW11nw8WW1NfAIRbjuKy8zeQVTFvoXl6QY3o4YJ0,1534
4
+ dtsdance/dflow.py,sha256=v6tQv9O6-xjXhNuU14vys0lIxi4hKKv27nuBjLgRk6Y,7747
5
+ dtsdance/dsyncer.py,sha256=PCatQIFXgi1l2qT60zNN-ALAqtv7_sg-D2oNMB_t8qw,9906
6
+ dtsdance/feishu_base.py,sha256=dYYNnsCjGZYN2I6inR9FRQoUiDLB7j40rzydw50N38o,11825
7
+ dtsdance/feishu_table.py,sha256=JleCjQJ-rcbC15v2UAc5BK-YioB2SMGsdGztQ9BaT4k,10779
8
+ dtsdance/metrics_fe.py,sha256=hzIl5BJmuCrkqJOHELVzXm3YAqrPttbyVkKBglS4mgQ,18978
9
+ dtsdance/s3.py,sha256=Bh-cwLksfO5PewNtIzE_Md3rRLDLI1DUVoOD7Pou5T8,1294
10
+ dtsdance/spacex.py,sha256=W_YGt_Wo_Cy4eDhgXhNfskayPHcZ1kg2Mil2ghI_VLE,4791
11
+ dtsdance/tcc_inner.py,sha256=QqVyhwP0kCdp1HFHFgtme9o0im-eQNn1HVGdLt3Xr8g,1627
12
+ dtsdance/tcc_open.py,sha256=JIfVhL3yfm0ftS_vzONE2QNzfqcZuX1mPTvjovAtwmE,6893
13
+ dtsdance/tce.py,sha256=QwqPimZu0GKeP--sdbMP9h1e22nGuZL2FHqBUykXDOw,11563
14
+ dts_dance-0.2.8.dist-info/METADATA,sha256=YjipvJ24oD--koIvidnlXrhll5UzI5Efpl-Zog_kQZc,826
15
+ dts_dance-0.2.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ dts_dance-0.2.8.dist-info/RECORD,,
dtsdance/bytecloud.py CHANGED
@@ -145,18 +145,23 @@ class ByteCloudClient:
145
145
  site_config = self.get_site_config(site)
146
146
  return self._acquire_jwt_token(site_config.endpoint, site_config.svc_secret)
147
147
 
148
- def build_request_headers(self, site: str) -> dict[str, str]:
148
+ def build_request_headers(self, site: str, vregion: str | None = None) -> dict[str, str]:
149
149
  """
150
150
  构建请求头
151
151
 
152
152
  Args:
153
153
  site: 站点名称
154
-
154
+ vregion: 默认为 None
155
155
  Returns:
156
156
  dict[str, str]: 请求头字典
157
157
  """
158
158
  jwt_token = self.get_jwt_token(site)
159
- return {"Content-Type": "application/json", "x-jwt-token": jwt_token}
159
+ headers = {"Content-Type": "application/json", "x-jwt-token": jwt_token}
160
+ if vregion:
161
+ headers["x-bcgw-vregion"] = vregion
162
+ headers["get-svc"] = "1" # response header 中显示实际请求的机房
163
+
164
+ return headers
160
165
 
161
166
  def get_site_config(self, site: str) -> SiteConfig:
162
167
  """
dtsdance/ddutil.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from typing import Any, Optional
2
2
  from loguru import logger
3
3
  import requests
4
+ import json
4
5
 
5
6
 
6
7
  def make_request(method: str, url: str, headers: dict[str, str], payload: Optional[dict] = None) -> dict[str, Any]:
@@ -37,3 +38,15 @@ def make_request(method: str, url: str, headers: dict[str, str], payload: Option
37
38
  error_msg += f", response.text: {response.text}"
38
39
  logger.warning(error_msg)
39
40
  raise
41
+
42
+
43
+ def output_curl(url: str, headers: dict[str, str], payload: dict) -> str:
44
+ """
45
+ 生成等效的 curl 命令字符串
46
+ """
47
+ curl_headers = " \\\n ".join([f"-H '{k}: {v}'" for k, v in headers.items()])
48
+ curl_payload = json.dumps(payload, ensure_ascii=False)
49
+ curl_cmd = f"""curl -X POST '{url}' \\
50
+ {curl_headers} \\
51
+ -d '{curl_payload}'"""
52
+ return curl_cmd
dtsdance/dflow.py CHANGED
@@ -1,8 +1,8 @@
1
1
  import collections
2
- from typing import Any, cast
2
+ from typing import Any
3
3
  from loguru import logger
4
4
 
5
- from dtsdance.ddutil import make_request
5
+ from dtsdance.ddutil import make_request, output_curl
6
6
  from .bytecloud import ByteCloudClient
7
7
 
8
8
 
@@ -19,12 +19,6 @@ class DFlowClient:
19
19
  def __init__(self, bytecloud_client: ByteCloudClient) -> None:
20
20
  self.bytecloud_client = bytecloud_client
21
21
 
22
- def build_request_headers(self, site: str, vregion: str | None) -> dict[str, str]:
23
- headers = self.bytecloud_client.build_request_headers(site)
24
- if vregion:
25
- headers["x-bcgw-vregion"] = vregion
26
- return headers
27
-
28
22
  def get_task_info(self, site: str, task_id: str, vregion: str | None = None) -> dict[str, Any]:
29
23
  """
30
24
  获取 DFlow 任务信息
@@ -43,7 +37,7 @@ class DFlowClient:
43
37
  # 构建请求数据
44
38
  payload = {"id": int(task_id)}
45
39
 
46
- response_data = make_request("POST", url, self.build_request_headers(site, vregion), payload)
40
+ response_data = make_request("POST", url, self.bytecloud_client.build_request_headers(site, vregion), payload)
47
41
 
48
42
  message = response_data.get("message")
49
43
  # logger.debug(f"get_task_info {site} {task_id}, message: {message}")
@@ -52,8 +46,7 @@ class DFlowClient:
52
46
  raise TaskNotFound(f"获取 DFlow 任务信息失败,站点: {site}, 任务 ID: {task_id} 不存在")
53
47
 
54
48
  try:
55
- data = cast(dict, response_data.get("data", {}))
56
- task = cast(dict, data.get("task", {}))
49
+ task = response_data.get("data", {}).get("task", {})
57
50
  # 提取核心信息
58
51
  filtered_data = {
59
52
  "task_id": task.get("id", ""),
@@ -85,7 +78,7 @@ class DFlowClient:
85
78
  # 构建请求数据
86
79
  payload = {"dflow_id": int(dflow_id)}
87
80
 
88
- response_data = make_request("POST", url, self.build_request_headers(site, vregion), payload)
81
+ response_data = make_request("POST", url, self.bytecloud_client.build_request_headers(site, vregion), payload)
89
82
 
90
83
  message = response_data.get("message", "")
91
84
  # logger.debug(f"get_dflow_info {site} {dflow_id}, message: {message}")
@@ -94,8 +87,7 @@ class DFlowClient:
94
87
  raise DFlowNotFound(f"获取 DFlow 进程信息失败,站点: {site}, 进程 ID: {dflow_id} 不存在")
95
88
 
96
89
  try:
97
- data = cast(dict, response_data.get("data", {}))
98
- dflow = cast(dict, data.get("dflow", {}))
90
+ dflow = response_data.get("data", {}).get("dflow", {})
99
91
  # 提取核心信息
100
92
  filtered_data = {
101
93
  "dflow_id": dflow.get("id", ""),
@@ -125,12 +117,13 @@ class DFlowClient:
125
117
  site_info = self.bytecloud_client.get_site_config(site)
126
118
  return f"{site_info.endpoint}/bytedts/datasync/detail/{task_id}"
127
119
 
128
- def init_resources(self, site: str, ctrl_env: str) -> bool:
120
+ def init_resources(self, site: str, vregion: str, ctrl_env: str) -> bool:
129
121
  """
130
122
  初始化 CTRL 环境资源
131
123
 
132
124
  Args:
133
125
  site: 站点名称
126
+ vregion: 虚拟区域
134
127
  ctrl_env: 控制环境
135
128
 
136
129
  Returns:
@@ -143,21 +136,21 @@ class DFlowClient:
143
136
  # 构建请求数据
144
137
  payload = {"ctrl_env": ctrl_env}
145
138
 
146
- response_data = make_request("POST", url, self.build_request_headers(site, None), payload)
139
+ response_data = make_request("POST", url, self.bytecloud_client.build_request_headers(site, vregion), payload)
147
140
 
148
141
  message = response_data.get("message")
149
142
  logger.info(f"int_resources {site} {ctrl_env}, message: {message}")
150
143
 
151
144
  return message == "ok"
152
145
 
153
- def list_resources(self, site: str, ctrl_env: str) -> list[str]:
146
+ def list_resources(self, site: str, vregion: str, ctrl_env: str) -> list[str]:
154
147
  """
155
148
  列举 CTRL 环境资源列表,连同 agent 列表
156
149
 
157
150
  Args:
158
151
  site: 站点名称
152
+ vregion: 虚拟区域
159
153
  ctrl_env: 控制环境
160
-
161
154
  Returns:
162
155
  list[str]: CTRL 环境资源列表
163
156
  """
@@ -166,9 +159,12 @@ class DFlowClient:
166
159
  url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
167
160
 
168
161
  # 构建请求数据
162
+ headers = self.bytecloud_client.build_request_headers(site, vregion)
169
163
  payload = {"offset": 0, "limit": 10, "ctrl_env": ctrl_env}
164
+ response_data = make_request("POST", url, headers, payload)
170
165
 
171
- response_data = make_request("POST", url, self.build_request_headers(site, None), payload)
166
+ # curl_cmd = output_curl(url, headers, payload)
167
+ # print(f"Equivalent curl:\n{curl_cmd}")
172
168
 
173
169
  try:
174
170
  data = response_data.get("data", {})
@@ -181,7 +177,7 @@ class DFlowClient:
181
177
  """获取所有可用的控制环境列表,返回以domain为键,ctrl_env列表为值的字典"""
182
178
  site_info = self.bytecloud_client.get_site_config(site)
183
179
  url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeRegions"
184
- resp_json = make_request("POST", url, self.build_request_headers(site, None), {})
180
+ resp_json = make_request("POST", url, self.bytecloud_client.build_request_headers(site, None), {})
185
181
  if resp_json["code"] != 0:
186
182
  raise Exception(resp_json["message"])
187
183
 
dtsdance/dsyncer.py CHANGED
@@ -21,7 +21,7 @@ class DSyncerClient:
21
21
  self.envs = envs
22
22
  self.bytecloud_client = bytecloud_client
23
23
 
24
- def _build_headers(self, env: str, secret_api: bool = False) -> dict[str, str]:
24
+ def _build_headers(self, site: str, secret_api: bool = False) -> dict[str, str]:
25
25
  """
26
26
  构建请求头
27
27
 
@@ -32,11 +32,11 @@ class DSyncerClient:
32
32
  Returns:
33
33
  dict[str, str]: 请求头字典
34
34
  """
35
- jwt_token = self.bytecloud_client.get_jwt_token(env)
36
- headers = {"X-Jwt-Token": jwt_token, "x-bcgw-vregion": env}
35
+ jwt_token = self.bytecloud_client.get_jwt_token(site)
36
+ headers = {"X-Jwt-Token": jwt_token}
37
37
 
38
38
  if secret_api:
39
- token = self.envs[env].token
39
+ token = self.envs[site].token
40
40
  headers["Authorization"] = f"Token {token}"
41
41
 
42
42
  return headers
dtsdance/spacex.py CHANGED
@@ -40,11 +40,11 @@ class SpaceXClient:
40
40
  logger.warning(f"do quest queryServerMeta exception: {e}")
41
41
  raise
42
42
 
43
- def register_resource(self, site: str, data_raw: dict[str, Any]) -> bool:
43
+ def register_resource(self, site: str, payload: dict[str, Any]) -> bool:
44
44
  site_info = self.bytecloud_client.get_site_config(site)
45
45
  url = f"{site_info.endpoint_bytedts_spacex}/resource/v1/registerResource"
46
46
  try:
47
- response = requests.post(url, json=data_raw, headers=self.bytecloud_client.build_request_headers(site))
47
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site))
48
48
  # print(f"response status code: {response.status_code}, response text: {response.text}")
49
49
 
50
50
  response.raise_for_status()
@@ -56,11 +56,11 @@ class SpaceXClient:
56
56
  logger.warning(f"do quest registerResource exception: {e}")
57
57
  raise
58
58
 
59
- def list_resources(self, site: str, data_raw: dict[str, Any]) -> list[str]:
59
+ def list_resources(self, site: str, payload: dict[str, Any]) -> list[str]:
60
60
  site_info = self.bytecloud_client.get_site_config(site)
61
61
  url = f"{site_info.endpoint_bytedts_spacex}/resource/v1/listResource"
62
62
  try:
63
- response = requests.post(url, json=data_raw, headers=self.bytecloud_client.build_request_headers(site))
63
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site))
64
64
  # print(f"response status code: {response.status_code}, response text: {response.text}")
65
65
 
66
66
  response.raise_for_status()
@@ -78,7 +78,7 @@ class SpaceXClient:
78
78
  def register_gateway(self, site: str, gateway_info: GatewayInfo) -> bool:
79
79
  site_info = self.bytecloud_client.get_site_config(site)
80
80
  url = f"{site_info.endpoint_bytedts_spacex}/bytedts/v1/registryGateway"
81
- data_raw = {
81
+ payload = {
82
82
  "region": gateway_info.mgr_name,
83
83
  "server_region": gateway_info.mgr_name,
84
84
  "cluster_region": gateway_info.ctrl_name,
@@ -97,7 +97,7 @@ class SpaceXClient:
97
97
  "unified_tao_service": 1,
98
98
  }
99
99
  try:
100
- response = requests.post(url, json=data_raw, headers=self.bytecloud_client.build_request_headers(site))
100
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site))
101
101
  response.raise_for_status()
102
102
 
103
103
  result = response.json()
@@ -107,11 +107,11 @@ class SpaceXClient:
107
107
  logger.warning(f"do quest registryGateway exception: {e}")
108
108
  raise
109
109
 
110
- def gateway_operation(self, site: str, operation: str, data_raw: dict[str, Any]) -> dict[str, Any]:
110
+ def gateway_operation(self, site: str, operation: str, payload: dict[str, Any]) -> dict[str, Any]:
111
111
  site_info = self.bytecloud_client.get_site_config(site)
112
112
  url = f"{site_info.endpoint_bytedts_spacex}/bytedts/v1/{operation}"
113
113
  try:
114
- response = requests.post(url, json=data_raw, headers=self.bytecloud_client.build_request_headers(site))
114
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site))
115
115
  # print(f"response status code: {response.status_code}, response text: {response.text}")
116
116
 
117
117
  response.raise_for_status()
dtsdance/tcc_inner.py CHANGED
@@ -27,7 +27,7 @@ class TCCInnerClient:
27
27
  site_info = self.bytecloud_client.get_site_config(site)
28
28
  url = f"{site_info.endpoint}/api/v3/tcc/bcc/config/list_v2"
29
29
 
30
- data_raw = {
30
+ payload = {
31
31
  "ns_name": ns_name,
32
32
  "region": region,
33
33
  "dir_path": dir,
@@ -40,7 +40,7 @@ class TCCInnerClient:
40
40
  }
41
41
 
42
42
  try:
43
- response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site), json=data_raw, timeout=30)
43
+ response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site), json=payload, timeout=30)
44
44
  response.raise_for_status()
45
45
  result = response.json()
46
46
 
dtsdance/tce.py ADDED
@@ -0,0 +1,289 @@
1
+ from typing import Any
2
+ import requests
3
+
4
+ from .bytecloud import ByteCloudClient
5
+
6
+
7
+ class TCEError(Exception):
8
+ pass
9
+
10
+
11
+ class TCEErrorServiceExist(Exception):
12
+ pass
13
+
14
+
15
+ class TCEErrorClusterExist(Exception):
16
+ pass
17
+
18
+
19
+ class TCEClient:
20
+ """Client for TCE deployment API."""
21
+
22
+ def __init__(self, bytecloud_client: ByteCloudClient) -> None:
23
+ """
24
+ Initialize TCE client.
25
+
26
+ Args:
27
+ base_url: TCE API base URL
28
+ jwt_token: JWT token for authentication (can be fetched automatically)
29
+ """
30
+ self.bytecloud_client = bytecloud_client
31
+
32
+ def create_service(self, site: str, ctrl_name: str, bytetree_parent_id: int) -> int | None:
33
+ site_info = self.bytecloud_client.get_site_config(site)
34
+ url = f"{site_info.endpoint}/api/v1/tce/open-apis/v1/deployment/service/create/"
35
+
36
+ service_info = {
37
+ "meta": {"name": f"bytedts.cm.{ctrl_name}", "psm": f"bytedts.cm.{ctrl_name}", "env": "prod"},
38
+ "auth": {
39
+ "galaxy_info": {"parent_id": bytetree_parent_id},
40
+ "approval_controls": ["reset"],
41
+ "iam_info": {"owners": [{"username": site_info.svc_account}, {"username": "mahang"}]},
42
+ "owner_name": site_info.svc_account,
43
+ },
44
+ "extra_features": {
45
+ "service_token": {
46
+ "tech_stack": {"language": "go", "framework": "custom", "platform_types_v_2": {"tce": ["http"]}},
47
+ "deploy": {"type": "stateless_compute", "scale": {"enable": True}, "descale": {"enable": True}},
48
+ "level": "P2",
49
+ "purpose": "convention",
50
+ "contain_user_personal_info": False,
51
+ "data_sensitivity_level": "L2",
52
+ }
53
+ },
54
+ "build": {
55
+ "dependency_mode": "default",
56
+ "base_image": "debian.bookworm.tce_service:latest",
57
+ "install_package_info": [],
58
+ "scm_repo_info": [
59
+ {"name": "bytedts/dflow/dtscm", "remote_id": 387073, "main_repo": True, "path": "dts-cm"},
60
+ {"name": "toutiao/load", "remote_id": 667, "path": "toutiao/load"},
61
+ {"name": "toutiao/runtime", "remote_id": 631, "path": "toutiao/runtime"},
62
+ ],
63
+ "language": "go",
64
+ "framework_support": "none",
65
+ "tech_stack": "custom",
66
+ },
67
+ "runtime": {
68
+ "umount_ss_conf": False,
69
+ "new_ports": [
70
+ {"intent": "primary", "port": 8088, "is_primary": True, "type": "tcp"},
71
+ {"intent": "other", "port": 3033, "is_primary": False, "type": "tcp"},
72
+ ],
73
+ "is_runtime": False,
74
+ "app_path": "dts-cm/bootstrap.sh",
75
+ "enable_iast": False,
76
+ },
77
+ }
78
+
79
+ payload = {"service_info": service_info}
80
+
81
+ try:
82
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site), timeout=60)
83
+ if response.status_code != 200:
84
+ if "already exists" in response.text:
85
+ raise TCEErrorServiceExist("TCE cluster with the same name already exists")
86
+
87
+ raise TCEError(f"TCE API request failed with status {response.status_code}\n" f"Response: {response.text}")
88
+
89
+ result = response.json()
90
+ if result.get("code", 0) == -1:
91
+ raise TCEError(f"TCE API error: {result.get('error')}")
92
+
93
+ return result.get("data", {}).get("service", 0)
94
+
95
+ except requests.RequestException as e:
96
+ raise TCEError(f"Failed to create TCE cluster: {str(e)}") from e
97
+
98
+ def create_cluster(self, site: str, cluster_info: dict[str, Any], service: int) -> int | None:
99
+ """
100
+ Create TCE cluster.
101
+
102
+ Args:
103
+ cluster_info: Cluster configuration (meta, resource, runtime)
104
+ service: Service ID
105
+
106
+ Returns:
107
+ The created cluster ID, or None if creation failed
108
+
109
+ Raises:
110
+ TCEError: If API call fails
111
+ """
112
+ site_info = self.bytecloud_client.get_site_config(site)
113
+ url = f"{site_info.endpoint}/api/v1/tce/open-apis/v1/deployment/cluster/create/"
114
+ payload = {"cluster_info": cluster_info, "service": service}
115
+
116
+ try:
117
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site), timeout=60)
118
+ if response.status_code != 200:
119
+ if "a same named TCE cluster already exists" in response.text:
120
+ raise TCEErrorClusterExist("TCE cluster with the same name already exists")
121
+
122
+ raise TCEError(f"TCE API request failed with status {response.status_code}\n" f"Response: {response.text}")
123
+
124
+ result = response.json()
125
+
126
+ # Check for errors in response
127
+ if not isinstance(result, dict):
128
+ raise TCEError(f"Unexpected response format: {result}")
129
+
130
+ # TCE API may return error information in different formats
131
+ if result.get("code", 0) == -1:
132
+ raise TCEError(f"TCE API error: {result.get('error')}")
133
+
134
+ data = result.get("data", {})
135
+ create_success = data.get("deployment_type", "") == "create" and data.get("status", "") == "pending"
136
+ if not create_success:
137
+ raise TCEError(f"TCE cluster creation failed: {result}")
138
+
139
+ cluster_id = data.get("cluster_info", {}).get("meta", {}).get("id", 0)
140
+ # Ensure cluster_id is an integer
141
+ return int(cluster_id) if cluster_id else 0
142
+
143
+ except requests.RequestException as e:
144
+ raise TCEError(f"Failed to create TCE cluster: {str(e)}") from e
145
+
146
+ def delete_cluster(self, site: str, cluster_id: int, service_id: int, cluster_name: str) -> int | None:
147
+ """
148
+ Delete TCE cluster (reset/destroy).
149
+
150
+ Args:
151
+ site: Site identifier
152
+ cluster_id: Cluster ID to delete
153
+ service_id: Service ID
154
+ cluster_name: Cluster name
155
+
156
+ Returns:
157
+ Response data
158
+
159
+ Raises:
160
+ TCEError: If API call fails
161
+ """
162
+ site_info = self.bytecloud_client.get_site_config(site)
163
+ url = f"{site_info.endpoint}/api/v1/tce/open-apis/v1/deployment/cluster/reset/"
164
+ payload = {"cluster": cluster_id, "service": service_id}
165
+
166
+ # print(f"Deleting TCE cluster {cluster_id} for service {service_id}")
167
+ try:
168
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site), timeout=60)
169
+ if response.status_code != 200:
170
+ raise TCEError(f"TCE API request failed with status {response.status_code}\n" f"Response: {response.text}")
171
+
172
+ result = response.json()
173
+ if result.get("code", 0) == -1:
174
+ error_msg = result.get("error", "")
175
+ if "do not exist" in error_msg:
176
+ print(f"cluster {cluster_name} not exist, maybe already deleted")
177
+ return None
178
+
179
+ raise TCEError(f"TCE API error: {error_msg}")
180
+
181
+ data = result.get("data", {})
182
+ # 查找 "人工确认" step 的 id
183
+ steps = data.get("steps", [])
184
+ for step in steps:
185
+ if step.get("step_type") == "ConfirmStep":
186
+ return step.get("id")
187
+
188
+ return None
189
+
190
+ except requests.RequestException as e:
191
+ raise TCEError(f"Failed to delete TCE cluster: {cluster_name}, msg: {str(e)}") from e
192
+
193
+ def get_cluster_info(self, site: str, cluster_id: int) -> dict[str, Any]:
194
+ site_info = self.bytecloud_client.get_site_config(site)
195
+ url = f"{site_info.endpoint}/api/v1/tce/open-apis/v1/clusters/{cluster_id}/"
196
+
197
+ try:
198
+ response = requests.get(url, headers=self.bytecloud_client.build_request_headers(site), timeout=60)
199
+ if response.status_code != 200:
200
+ raise TCEError(f"TCE API request failed with status {response.status_code}\n" f"Response: {response.text}")
201
+
202
+ # print(f"get_cluster_info response.text: {response.text}")
203
+ result = response.json()
204
+ if result.get("code", 0) == -1:
205
+ raise TCEError(f"TCE API error: {result.get('error')}")
206
+
207
+ return result.get("data", {})
208
+
209
+ except requests.RequestException as e:
210
+ raise TCEError(f"Failed to create TCE cluster: {str(e)}") from e
211
+
212
+ def is_cluster_running(self, site: str, cluster_id: int | None) -> bool:
213
+ if cluster_id is None:
214
+ return False
215
+
216
+ cluster_info = self.get_cluster_info(site, cluster_id)
217
+ return cluster_info.get("meta", {}).get("status", "unknown") == "running"
218
+
219
+ def restart_cluster(self, site: str, service_id: int, cluster_id: int) -> list[int]:
220
+ site_info = self.bytecloud_client.get_site_config(site)
221
+ url = f"{site_info.endpoint}/api/v1/tce/open-apis/v1/deployment/cluster/pod_exec/"
222
+
223
+ payload = {
224
+ "service": service_id,
225
+ "cluster": cluster_id,
226
+ "exec_command": "rebuild",
227
+ "exec_params": {
228
+ "surge_percent": 25,
229
+ "ready_seconds": 10,
230
+ "timeout": 120,
231
+ "tolerance_num": 3,
232
+ "failed_policy": "remain",
233
+ "need_check": True,
234
+ },
235
+ "exec_type": "rebuild",
236
+ "parallel_mode": True,
237
+ }
238
+
239
+ try:
240
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site), timeout=60)
241
+ if response.status_code != 200:
242
+ raise TCEError(f"TCE API request failed with status {response.status_code}\n" f"Response: {response.text}")
243
+
244
+ result = response.json()
245
+ if result.get("code", 0) == -1:
246
+ raise TCEError(f"TCE API error: {result.get('error')}")
247
+
248
+ steps = result.get("data", {}).get("steps", [])
249
+ step_ids = [step.get("id", 0) for step in steps]
250
+ return step_ids
251
+
252
+ except requests.RequestException as e:
253
+ raise TCEError(f"Failed to create TCE cluster: {str(e)}") from e
254
+
255
+ def confirm_step(self, site: str, step_id: int, action: str = "confirm") -> bool:
256
+ """
257
+ Confirm a step in TCE cluster deployment.
258
+
259
+ Args:
260
+ step_id: Step ID
261
+
262
+ Returns:
263
+ Response data
264
+
265
+ Raises:
266
+ TCEError: If API call fails
267
+ """
268
+ site_info = self.bytecloud_client.get_site_config(site)
269
+ url = f"{site_info.endpoint}/api/v1/tce/open-apis/v1/deployment_steps/{step_id}/actions/"
270
+ payload = {"action": action}
271
+
272
+ try:
273
+ response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site), timeout=60)
274
+ # print(f"Confirm step response: {response.text}")
275
+
276
+ response.raise_for_status()
277
+ result = response.json()
278
+
279
+ # Check for errors in response
280
+ if not isinstance(result, dict):
281
+ raise TCEError(f"Unexpected response format: {result}")
282
+
283
+ if result.get("code", 0) == -1:
284
+ raise TCEError(f"TCE API error: {result.get('error')}")
285
+
286
+ return True
287
+
288
+ except requests.RequestException as e:
289
+ raise TCEError(f"Failed to confirm ticket, msg: {str(e)}") from e
@@ -1,15 +0,0 @@
1
- dtsdance/__init__.py,sha256=Yl_jEZ5weYfcrklnDvwB4wSgCOvMBLRRgWx0gHs3qfM,49
2
- dtsdance/bytecloud.py,sha256=s1suo-YexMH6gUHYwC6P5k6AeyOm7Q_acYh_gHCDVjI,5654
3
- dtsdance/ddutil.py,sha256=aZ-fG_e2ZkHs8sxZsudf6bVCnp_OI2Z4WjbxOfPoJRQ,1134
4
- dtsdance/dflow.py,sha256=lEn_gd0pUOqEhuvs0hSQICR8LKOk3wP4DBbeyHZ-A44,7766
5
- dtsdance/dsyncer.py,sha256=gK2rRiO3041P3efbmXgvLPeTgKlJowuPR7CCz_uyoSA,9926
6
- dtsdance/feishu_base.py,sha256=dYYNnsCjGZYN2I6inR9FRQoUiDLB7j40rzydw50N38o,11825
7
- dtsdance/feishu_table.py,sha256=JleCjQJ-rcbC15v2UAc5BK-YioB2SMGsdGztQ9BaT4k,10779
8
- dtsdance/metrics_fe.py,sha256=hzIl5BJmuCrkqJOHELVzXm3YAqrPttbyVkKBglS4mgQ,18978
9
- dtsdance/s3.py,sha256=Bh-cwLksfO5PewNtIzE_Md3rRLDLI1DUVoOD7Pou5T8,1294
10
- dtsdance/spacex.py,sha256=oVnHXTj-cuq2vcsUJq33_aaq0TQp7A52kldvspKueI4,4799
11
- dtsdance/tcc_inner.py,sha256=Z0Wr-z2YPwAti6BO_fsX0VPT5pCbfuCBLQ37UKMFL7U,1629
12
- dtsdance/tcc_open.py,sha256=JIfVhL3yfm0ftS_vzONE2QNzfqcZuX1mPTvjovAtwmE,6893
13
- dts_dance-0.2.5.dist-info/METADATA,sha256=-skiMz-CuBlzpt7BBDJpOEXrmsj8np_xKnG-6K_HPao,826
14
- dts_dance-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
- dts_dance-0.2.5.dist-info/RECORD,,