dts-dance 0.1.9__py3-none-any.whl → 0.2.1__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.
- {dts_dance-0.1.9.dist-info → dts_dance-0.2.1.dist-info}/METADATA +1 -1
- dts_dance-0.2.1.dist-info/RECORD +14 -0
- dtsdance/bytecloud.py +40 -34
- dtsdance/dflow.py +77 -45
- dtsdance/dsyncer.py +48 -48
- dtsdance/metrics_fe.py +13 -13
- dtsdance/spacex.py +46 -47
- dtsdance/tcc_inner.py +55 -0
- dtsdance/{tcc.py → tcc_open.py} +23 -14
- dts_dance-0.1.9.dist-info/RECORD +0 -13
- {dts_dance-0.1.9.dist-info → dts_dance-0.2.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
dtsdance/__init__.py,sha256=Yl_jEZ5weYfcrklnDvwB4wSgCOvMBLRRgWx0gHs3qfM,49
|
|
2
|
+
dtsdance/bytecloud.py,sha256=SN82KIYAfq16s06-HUoj37PbgSbUa7MoFFcah2kaXOk,5213
|
|
3
|
+
dtsdance/dflow.py,sha256=N9u7uB9UyUbB2FswBezXB9SbwmlDr3PWhY_eUZJeQ-8,7064
|
|
4
|
+
dtsdance/dsyncer.py,sha256=X59sKDvK6rDeumCajepcqSSJysDfvJByYZAhcdOyNLw,11179
|
|
5
|
+
dtsdance/feishu_base.py,sha256=2j4ZM4PFqJ-9EhC6DQ1OmAg--3VBGZyyRuxyjL0j6OU,3733
|
|
6
|
+
dtsdance/feishu_table.py,sha256=ZUeoKrM4nmm5hFhc3vWOVYeLP390orm-284Od92G4iQ,8424
|
|
7
|
+
dtsdance/metrics_fe.py,sha256=hzIl5BJmuCrkqJOHELVzXm3YAqrPttbyVkKBglS4mgQ,18978
|
|
8
|
+
dtsdance/s3.py,sha256=Bh-cwLksfO5PewNtIzE_Md3rRLDLI1DUVoOD7Pou5T8,1294
|
|
9
|
+
dtsdance/spacex.py,sha256=eYPx3-VKgPK6-IO8-HPuX0-mxsM6kt1HMZqyDA_KPbw,2340
|
|
10
|
+
dtsdance/tcc_inner.py,sha256=xy3N1N3BNl1oc2oMNHKhnsj47rAKOOJ-aXw8GtA2De8,1621
|
|
11
|
+
dtsdance/tcc_open.py,sha256=Qb_ue3xH0CSsoReItdFKuvW7JsDTfFkDNIB_4zjzSQI,6893
|
|
12
|
+
dts_dance-0.2.1.dist-info/METADATA,sha256=QQohyobEcAlWjdpCDwPBXzYlSRBZxeGcV-xfN9_ladU,793
|
|
13
|
+
dts_dance-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
dts_dance-0.2.1.dist-info/RECORD,,
|
dtsdance/bytecloud.py
CHANGED
|
@@ -6,27 +6,29 @@ from loguru import logger
|
|
|
6
6
|
from typing import NamedTuple
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
9
|
+
class SiteConfig(NamedTuple):
|
|
10
10
|
name: str
|
|
11
11
|
endpoint: str
|
|
12
|
-
|
|
12
|
+
svc_account: str
|
|
13
|
+
svc_secret: str
|
|
14
|
+
endpoint_bytedts_spacex: str | None = None
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class
|
|
17
|
+
class ByteCloudClient:
|
|
16
18
|
"""
|
|
17
|
-
ByteCloud
|
|
19
|
+
ByteCloud Client
|
|
18
20
|
"""
|
|
19
21
|
|
|
20
22
|
# 每小时刷新一次,单位为秒
|
|
21
23
|
_REFRESH_INTERVAL = 1 * 60 * 60
|
|
22
24
|
|
|
23
|
-
def __init__(self,
|
|
25
|
+
def __init__(self, sites: dict[str, SiteConfig]):
|
|
24
26
|
"""
|
|
25
|
-
初始化 ByteCloud
|
|
27
|
+
初始化 ByteCloud Client
|
|
26
28
|
从配置文件加载所有环境的信息,并为每个环境初始化 JWT 令牌
|
|
27
|
-
|
|
29
|
+
sites 中保存内容 list[(name, endpoint, svc_account, svc_secret)]
|
|
28
30
|
"""
|
|
29
|
-
self.
|
|
31
|
+
self.sites = sites
|
|
30
32
|
|
|
31
33
|
# 初始化线程锁,用于保护 jwt_tokens 的并发访问
|
|
32
34
|
self.token_lock = threading.Lock()
|
|
@@ -66,20 +68,20 @@ class ByteCloudHelper:
|
|
|
66
68
|
"""
|
|
67
69
|
logger.debug("开始刷新所有环境的 JWT 令牌...")
|
|
68
70
|
|
|
69
|
-
for _,
|
|
71
|
+
for _, site in self.sites.items():
|
|
70
72
|
try:
|
|
71
73
|
# 刷新令牌
|
|
72
|
-
new_token = self._acquire_jwt_token(
|
|
74
|
+
new_token = self._acquire_jwt_token(site.endpoint, site.svc_secret)
|
|
73
75
|
# 使用线程锁更新缓存中的 JWT 令牌
|
|
74
76
|
with self.token_lock:
|
|
75
|
-
self.jwt_tokens[
|
|
76
|
-
logger.debug(f"环境 {
|
|
77
|
+
self.jwt_tokens[site.name] = new_token
|
|
78
|
+
logger.debug(f"环境 {site.name} 的 JWT 令牌成功刷新,新令牌: {new_token}")
|
|
77
79
|
except Exception as e:
|
|
78
|
-
logger.error(f"环境 {
|
|
80
|
+
logger.error(f"环境 {site.name} 的 JWT 令牌刷新失败: {e}")
|
|
79
81
|
|
|
80
82
|
logger.debug(f"所有环境的 JWT 令牌已成功刷新。jwt_tokens: {self.jwt_tokens}")
|
|
81
83
|
|
|
82
|
-
def _acquire_jwt_token(self, endpoint: str,
|
|
84
|
+
def _acquire_jwt_token(self, endpoint: str, svc_secret: str) -> str:
|
|
83
85
|
"""
|
|
84
86
|
获取 JWT 令牌
|
|
85
87
|
|
|
@@ -93,7 +95,7 @@ class ByteCloudHelper:
|
|
|
93
95
|
url = endpoint + "/auth/api/v1/jwt"
|
|
94
96
|
headers = {
|
|
95
97
|
"Content-Type": "application/json",
|
|
96
|
-
"Authorization": "Bearer " +
|
|
98
|
+
"Authorization": "Bearer " + svc_secret,
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
try:
|
|
@@ -125,39 +127,43 @@ class ByteCloudHelper:
|
|
|
125
127
|
logger.error(f"获取JWT令牌时出错: {e}")
|
|
126
128
|
raise
|
|
127
129
|
|
|
128
|
-
def get_jwt_token(self,
|
|
130
|
+
def get_jwt_token(self, site: str) -> str:
|
|
129
131
|
"""
|
|
130
|
-
|
|
132
|
+
获取指定站点的 JWT 令牌
|
|
133
|
+
"""
|
|
134
|
+
# 使用线程锁保护并发访问
|
|
135
|
+
with self.token_lock:
|
|
136
|
+
if site not in self.jwt_tokens:
|
|
137
|
+
raise KeyError(f"站点 {site} 的 JWT 令牌不存在")
|
|
138
|
+
return self.jwt_tokens[site]
|
|
139
|
+
|
|
140
|
+
def build_request_headers(self, site: str) -> dict[str, str]:
|
|
141
|
+
"""
|
|
142
|
+
构建请求头
|
|
131
143
|
|
|
132
144
|
Args:
|
|
133
|
-
|
|
145
|
+
site: 站点名称
|
|
134
146
|
|
|
135
147
|
Returns:
|
|
136
|
-
str:
|
|
137
|
-
|
|
138
|
-
Raises:
|
|
139
|
-
KeyError: 如果指定的环境不存在
|
|
148
|
+
dict[str, str]: 请求头字典
|
|
140
149
|
"""
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if env not in self.jwt_tokens:
|
|
144
|
-
raise KeyError(f"环境 {env} 的 JWT 令牌不存在")
|
|
145
|
-
return self.jwt_tokens[env]
|
|
150
|
+
jwt_token = self.get_jwt_token(site)
|
|
151
|
+
return {"Content-Type": "application/json", "x-jwt-token": jwt_token}
|
|
146
152
|
|
|
147
|
-
def
|
|
153
|
+
def get_site_info(self, site: str) -> SiteConfig:
|
|
148
154
|
"""
|
|
149
155
|
获取指定环境的信息
|
|
150
156
|
|
|
151
157
|
Args:
|
|
152
|
-
|
|
158
|
+
site: 站点名称
|
|
153
159
|
|
|
154
160
|
Returns:
|
|
155
|
-
|
|
161
|
+
SiteConfig: 指定站点的信息
|
|
156
162
|
|
|
157
163
|
Raises:
|
|
158
|
-
KeyError:
|
|
164
|
+
KeyError: 如果指定的站点不存在
|
|
159
165
|
"""
|
|
160
|
-
if
|
|
161
|
-
raise KeyError(f"
|
|
166
|
+
if site not in self.sites:
|
|
167
|
+
raise KeyError(f"站点 {site} 不存在")
|
|
162
168
|
|
|
163
|
-
return self.
|
|
169
|
+
return self.sites[site]
|
dtsdance/dflow.py
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
from typing import Any, cast, Optional
|
|
2
2
|
from loguru import logger
|
|
3
|
-
from .bytecloud import
|
|
3
|
+
from .bytecloud import ByteCloudClient
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class TaskNotFound(Exception):
|
|
8
|
+
pass
|
|
8
9
|
|
|
9
|
-
def __init__(self, bytecloud_helper: ByteCloudHelper) -> None:
|
|
10
|
-
self.bytecloud_helper = bytecloud_helper
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
构建请求头
|
|
11
|
+
class DFlowNotFound(Exception):
|
|
12
|
+
pass
|
|
15
13
|
|
|
16
|
-
Args:
|
|
17
|
-
env: 环境名称
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
headers = {"x-jwt-token": jwt_token}
|
|
24
|
-
return headers
|
|
15
|
+
class DFlowClient:
|
|
16
|
+
|
|
17
|
+
def __init__(self, bytecloud_client: ByteCloudClient) -> None:
|
|
18
|
+
self.bytecloud_client = bytecloud_client
|
|
25
19
|
|
|
26
20
|
def _make_request(self, method: str, url: str, headers: dict[str, str], json_data: Optional[dict] = None) -> dict[str, Any]:
|
|
27
21
|
"""
|
|
@@ -58,30 +52,31 @@ class DFlowHelper:
|
|
|
58
52
|
logger.warning(error_msg)
|
|
59
53
|
raise
|
|
60
54
|
|
|
61
|
-
def
|
|
55
|
+
def get_task_info(self, site: str, task_id: str) -> dict[str, Any]:
|
|
62
56
|
"""
|
|
63
57
|
获取 DFlow 任务信息
|
|
64
58
|
|
|
65
59
|
Args:
|
|
66
|
-
|
|
60
|
+
site: 站点名称
|
|
67
61
|
task_id: DFlow 任务 ID
|
|
68
62
|
|
|
69
63
|
Returns:
|
|
70
64
|
dict[str, Any]: DFlow 任务信息,包含 create_time 等字段
|
|
71
65
|
"""
|
|
72
66
|
# 构建 API URL
|
|
73
|
-
|
|
74
|
-
url = f"{
|
|
75
|
-
|
|
76
|
-
# 准备请求头
|
|
77
|
-
headers = self._build_headers(env)
|
|
67
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
68
|
+
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeTaskInfo"
|
|
78
69
|
|
|
79
70
|
# 构建请求数据
|
|
80
71
|
json_data = {"id": int(task_id)}
|
|
81
72
|
|
|
82
|
-
response_data = self._make_request("POST", url,
|
|
73
|
+
response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
|
|
74
|
+
|
|
75
|
+
message = response_data.get("message")
|
|
76
|
+
# logger.debug(f"get_task_info {site} {task_id}, message: {message}")
|
|
83
77
|
|
|
84
|
-
|
|
78
|
+
if message == "task not exists":
|
|
79
|
+
raise TaskNotFound(f"获取 DFlow 任务信息失败,站点: {site}, 任务 ID: {task_id} 不存在")
|
|
85
80
|
|
|
86
81
|
try:
|
|
87
82
|
data = cast(dict, response_data.get("data", {}))
|
|
@@ -99,71 +94,108 @@ class DFlowHelper:
|
|
|
99
94
|
except (KeyError, AttributeError, Exception) as e:
|
|
100
95
|
raise Exception(f"无法从响应中提取 DFlow 任务信息数据: {str(e)}")
|
|
101
96
|
|
|
102
|
-
def
|
|
97
|
+
def get_dflow_info(self, site: str, dflow_id: str) -> dict[str, Any]:
|
|
98
|
+
"""
|
|
99
|
+
获取 DFlow 进程信息
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
site: 站点名称
|
|
103
|
+
dflow_id: DFlow 进程 ID
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
dict[str, Any]: DFlow 进程信息,包含 create_time 等字段
|
|
107
|
+
"""
|
|
108
|
+
# 构建 API URL
|
|
109
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
110
|
+
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeDFlowDetail"
|
|
111
|
+
|
|
112
|
+
# 构建请求数据
|
|
113
|
+
json_data = {"dflow_id": int(dflow_id)}
|
|
114
|
+
|
|
115
|
+
response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
|
|
116
|
+
|
|
117
|
+
message = response_data.get("message", "")
|
|
118
|
+
# logger.debug(f"get_dflow_info {site} {dflow_id}, message: {message}")
|
|
119
|
+
|
|
120
|
+
if "dflow not found" in message:
|
|
121
|
+
raise DFlowNotFound(f"获取 DFlow 进程信息失败,站点: {site}, 进程 ID: {dflow_id} 不存在")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
data = cast(dict, response_data.get("data", {}))
|
|
125
|
+
dflow = cast(dict, data.get("dflow", {}))
|
|
126
|
+
# 提取核心信息
|
|
127
|
+
filtered_data = {
|
|
128
|
+
"dflow_id": dflow.get("id", ""),
|
|
129
|
+
"task_id": dflow.get("task_id", ""),
|
|
130
|
+
"app": dflow.get("app", ""),
|
|
131
|
+
"schedule_plan_name": dflow.get("schedule_plan_name", ""),
|
|
132
|
+
"running_state.healthy_status": dflow.get("running_state", {}).get("healthy_status", ""),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return filtered_data
|
|
136
|
+
|
|
137
|
+
except (KeyError, AttributeError, Exception) as e:
|
|
138
|
+
raise Exception(f"无法从响应中提取 DFlow 进程信息数据: {str(e)}")
|
|
139
|
+
|
|
140
|
+
def generate_task_url(self, site: str, task_id: str) -> str:
|
|
103
141
|
"""
|
|
104
142
|
获取 DFlow 任务详情页面的 URL
|
|
105
143
|
|
|
106
144
|
Args:
|
|
107
|
-
|
|
145
|
+
site: 站点名称
|
|
108
146
|
task_id: DFlow 任务 ID
|
|
109
147
|
|
|
110
148
|
Returns:
|
|
111
149
|
str: DFlow 任务详情页面的 URL
|
|
112
150
|
"""
|
|
113
151
|
# 根据环境生成对应的 scope 参数
|
|
114
|
-
|
|
115
|
-
return f"{
|
|
152
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
153
|
+
return f"{site_info.endpoint}/bytedts/datasync/detail/{task_id}?scope={site}"
|
|
116
154
|
|
|
117
|
-
def init_resources(self,
|
|
155
|
+
def init_resources(self, site: str, ctrl_env: str) -> bool:
|
|
118
156
|
"""
|
|
119
157
|
初始化 CTRL 环境资源
|
|
120
158
|
|
|
121
159
|
Args:
|
|
122
|
-
|
|
160
|
+
site: 站点名称
|
|
123
161
|
ctrl_env: 控制环境
|
|
124
162
|
|
|
125
163
|
Returns:
|
|
126
164
|
bool: CTRL 环境资源初始化结果
|
|
127
165
|
"""
|
|
128
166
|
# 构建 API URL
|
|
129
|
-
|
|
130
|
-
url = f"{
|
|
131
|
-
|
|
132
|
-
# 准备请求头
|
|
133
|
-
headers = self._build_headers(env)
|
|
167
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
168
|
+
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/InitSystemResource"
|
|
134
169
|
|
|
135
170
|
# 构建请求数据
|
|
136
171
|
json_data = {"ctrl_env": ctrl_env}
|
|
137
172
|
|
|
138
|
-
response_data = self._make_request("POST", url,
|
|
173
|
+
response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
|
|
139
174
|
|
|
140
175
|
message = response_data.get("message")
|
|
141
|
-
logger.info(f"int_resources {
|
|
176
|
+
logger.info(f"int_resources {site} {ctrl_env}, message: {message}")
|
|
142
177
|
|
|
143
178
|
return message == "ok"
|
|
144
179
|
|
|
145
|
-
def list_resources(self,
|
|
180
|
+
def list_resources(self, site: str, ctrl_env: str) -> list[str]:
|
|
146
181
|
"""
|
|
147
182
|
列举 CTRL 环境资源列表
|
|
148
183
|
|
|
149
184
|
Args:
|
|
150
|
-
|
|
185
|
+
site: 站点名称
|
|
151
186
|
ctrl_env: 控制环境
|
|
152
187
|
|
|
153
188
|
Returns:
|
|
154
189
|
list[str]: CTRL 环境资源列表
|
|
155
190
|
"""
|
|
156
191
|
# 构建 API URL
|
|
157
|
-
|
|
158
|
-
url = f"{
|
|
159
|
-
|
|
160
|
-
# 准备请求头
|
|
161
|
-
headers = self._build_headers(env)
|
|
192
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
193
|
+
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
|
|
162
194
|
|
|
163
195
|
# 构建请求数据
|
|
164
196
|
json_data = {"offset": 0, "limit": 10, "ctrl_env": ctrl_env}
|
|
165
197
|
|
|
166
|
-
response_data = self._make_request("POST", url,
|
|
198
|
+
response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
|
|
167
199
|
|
|
168
200
|
try:
|
|
169
201
|
data = cast(dict, response_data.get("data", {}))
|
dtsdance/dsyncer.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .bytecloud import
|
|
1
|
+
from .bytecloud import ByteCloudClient
|
|
2
2
|
from typing import Any, NamedTuple, Tuple, cast, Optional
|
|
3
3
|
from loguru import logger
|
|
4
4
|
import requests
|
|
@@ -15,11 +15,11 @@ class DSyncerEnvInfo(NamedTuple):
|
|
|
15
15
|
token: str
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class
|
|
18
|
+
class DSyncerClient:
|
|
19
19
|
|
|
20
|
-
def __init__(self, envs: dict[str, DSyncerEnvInfo],
|
|
20
|
+
def __init__(self, envs: dict[str, DSyncerEnvInfo], bytecloud_client: ByteCloudClient) -> None:
|
|
21
21
|
self.envs = envs
|
|
22
|
-
self.
|
|
22
|
+
self.bytecloud_client = bytecloud_client
|
|
23
23
|
|
|
24
24
|
def _build_headers(self, env: str, secret_api: bool = False) -> dict[str, str]:
|
|
25
25
|
"""
|
|
@@ -32,7 +32,7 @@ class DSyncerHelper:
|
|
|
32
32
|
Returns:
|
|
33
33
|
dict[str, str]: 请求头字典
|
|
34
34
|
"""
|
|
35
|
-
jwt_token = self.
|
|
35
|
+
jwt_token = self.bytecloud_client.get_jwt_token(env)
|
|
36
36
|
headers = {"X-Jwt-Token": jwt_token, "x-bcgw-vregion": env}
|
|
37
37
|
|
|
38
38
|
if secret_api:
|
|
@@ -41,7 +41,7 @@ class DSyncerHelper:
|
|
|
41
41
|
|
|
42
42
|
return headers
|
|
43
43
|
|
|
44
|
-
def _make_request(self, method: str, url: str, headers: dict[str, str],
|
|
44
|
+
def _make_request(self, method: str, url: str, headers: dict[str, str], data_raw: Optional[dict] = None) -> dict[str, Any]:
|
|
45
45
|
"""
|
|
46
46
|
发送 HTTP 请求的通用方法
|
|
47
47
|
|
|
@@ -60,7 +60,7 @@ class DSyncerHelper:
|
|
|
60
60
|
if method.upper() == "GET":
|
|
61
61
|
response = requests.get(url, headers=headers)
|
|
62
62
|
elif method.upper() == "POST":
|
|
63
|
-
response = requests.post(url, json=
|
|
63
|
+
response = requests.post(url, json=data_raw, headers=headers)
|
|
64
64
|
else:
|
|
65
65
|
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
|
66
66
|
|
|
@@ -77,41 +77,41 @@ class DSyncerHelper:
|
|
|
77
77
|
logger.warning(error_msg)
|
|
78
78
|
raise
|
|
79
79
|
|
|
80
|
-
def _acquire_task_info(self,
|
|
80
|
+
def _acquire_task_info(self, site: str, task_id: str) -> dict[str, str]:
|
|
81
81
|
"""
|
|
82
82
|
获取 DSyncer 任务信息
|
|
83
83
|
|
|
84
84
|
Args:
|
|
85
|
-
|
|
85
|
+
site: 站点名称
|
|
86
86
|
task_id: DSyncer 任务 ID
|
|
87
87
|
|
|
88
88
|
Returns:
|
|
89
89
|
dict[str, str]: DSyncer 任务的 rocket_mq_connection 信息,只包含 cluster、topic 和 group 字段
|
|
90
90
|
"""
|
|
91
91
|
# 构建 API URL
|
|
92
|
-
|
|
93
|
-
url = f"{
|
|
92
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
93
|
+
url = f"{site_info.endpoint}/api/v1/dsyncer/openapi/taskinfo/{task_id}/"
|
|
94
94
|
|
|
95
95
|
# 准备请求头
|
|
96
|
-
headers = self._build_headers(
|
|
96
|
+
headers = self._build_headers(site)
|
|
97
97
|
|
|
98
98
|
return self._make_request("GET", url, headers)
|
|
99
99
|
|
|
100
|
-
def get_dflow_task_info(self,
|
|
100
|
+
def get_dflow_task_info(self, site: str, task_id: str) -> tuple[str, str]:
|
|
101
101
|
"""
|
|
102
102
|
获取迁移后的 DFlow 任务信息
|
|
103
103
|
"""
|
|
104
104
|
# 构建 API URL
|
|
105
|
-
|
|
106
|
-
url = f"{
|
|
105
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
106
|
+
url = f"{site_info.endpoint}/api/v1/dsyncer/openapi/taskinfo/{task_id}/migrate/"
|
|
107
107
|
|
|
108
108
|
# 准备请求头
|
|
109
|
-
headers = self._build_headers(
|
|
109
|
+
headers = self._build_headers(site)
|
|
110
110
|
|
|
111
111
|
json_data = self._make_request("GET", url, headers)
|
|
112
112
|
|
|
113
113
|
message = json_data.get("message", "")
|
|
114
|
-
logger.debug(f"get task migrate info {
|
|
114
|
+
logger.debug(f"get task migrate info {site} {task_id}, message: {message}")
|
|
115
115
|
|
|
116
116
|
# 从消息中提取 DFlow 任务 URL 和 ID
|
|
117
117
|
# 消息格式: "任务已迁移至ByteDTS平台,请前往[https://cloud.bytedance.net/bytedts/datasync/detail/93127366537986?scope=China-North&tabKey=DetailInfo]查看详情"
|
|
@@ -133,7 +133,7 @@ class DSyncerHelper:
|
|
|
133
133
|
logger.warning(f"could not extract dflow task info from message: {message}")
|
|
134
134
|
return ("", "")
|
|
135
135
|
|
|
136
|
-
def generate_task_url(self,
|
|
136
|
+
def generate_task_url(self, site: str, task_id: str) -> str:
|
|
137
137
|
"""
|
|
138
138
|
获取 DSyncer 任务详情页面的 URL
|
|
139
139
|
|
|
@@ -144,8 +144,8 @@ class DSyncerHelper:
|
|
|
144
144
|
Returns:
|
|
145
145
|
str: DSyncer 任务详情页面的 URL
|
|
146
146
|
"""
|
|
147
|
-
|
|
148
|
-
return DSyncer_Task_Detail_URL.format(endpoint=
|
|
147
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
148
|
+
return DSyncer_Task_Detail_URL.format(endpoint=site_info.endpoint, task_id=task_id)
|
|
149
149
|
|
|
150
150
|
def generate_task_grafana_url(self, task_id: str, change_time: str) -> str:
|
|
151
151
|
"""
|
|
@@ -200,7 +200,7 @@ class DSyncerHelper:
|
|
|
200
200
|
except (KeyError, AttributeError, Exception) as e:
|
|
201
201
|
raise Exception(f"无法从响应中提取 DSyncer 任务状态数据: {str(e)}")
|
|
202
202
|
|
|
203
|
-
def is_task_migrate_running(self,
|
|
203
|
+
def is_task_migrate_running(self, site: str, task_id: str) -> Tuple[bool, str]:
|
|
204
204
|
"""
|
|
205
205
|
检查 DSyncer 任务是否正在迁移中
|
|
206
206
|
|
|
@@ -208,62 +208,62 @@ class DSyncerHelper:
|
|
|
208
208
|
bool: 如果任务正在迁移中,返回 True;否则返回 False
|
|
209
209
|
"""
|
|
210
210
|
# 构建 API URL
|
|
211
|
-
|
|
212
|
-
url = f"{
|
|
211
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
212
|
+
url = f"{site_info.endpoint}/api/v1/dsyncer/secret_api/task/migrate/check"
|
|
213
213
|
|
|
214
214
|
# 准备请求头
|
|
215
|
-
headers = self._build_headers(
|
|
215
|
+
headers = self._build_headers(site, secret_api=True)
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
data_raw = {"task_id": task_id}
|
|
218
218
|
|
|
219
|
-
response_data = self._make_request("POST", url, headers,
|
|
219
|
+
response_data = self._make_request("POST", url, headers, data_raw)
|
|
220
220
|
|
|
221
221
|
message = response_data.get("message", "")
|
|
222
|
-
logger.debug(f"get task migrate status {
|
|
222
|
+
logger.debug(f"get task migrate status {site} {task_id}, message: {message}")
|
|
223
223
|
if "task migrate is running" in message:
|
|
224
224
|
return True, ""
|
|
225
225
|
else:
|
|
226
226
|
return False, response_data.get("data", {}).get("msg", {})
|
|
227
227
|
|
|
228
|
-
def migration_rollback(self,
|
|
228
|
+
def migration_rollback(self, site: str, task_id: str) -> bool:
|
|
229
229
|
"""
|
|
230
230
|
执行回滚
|
|
231
231
|
"""
|
|
232
232
|
# 构建 API URL
|
|
233
|
-
|
|
234
|
-
url = f"{
|
|
233
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
234
|
+
url = f"{site_info.endpoint}/api/v1/dsyncer/secret_api/task/rollback_migrate2dsyncer/"
|
|
235
235
|
|
|
236
236
|
# 准备请求头
|
|
237
|
-
headers = self._build_headers(
|
|
237
|
+
headers = self._build_headers(site, secret_api=True)
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
data_raw = {"task_id_list": [task_id]}
|
|
240
240
|
|
|
241
|
-
response_data = self._make_request("POST", url, headers,
|
|
241
|
+
response_data = self._make_request("POST", url, headers, data_raw)
|
|
242
242
|
|
|
243
|
-
logger.debug(f"migration_mark_rollback return {
|
|
243
|
+
logger.debug(f"migration_mark_rollback return {site} {task_id}, json_data: {response_data}")
|
|
244
244
|
success_task = response_data.get("data", {}).get("success_task", [])
|
|
245
245
|
return task_id in success_task
|
|
246
246
|
|
|
247
|
-
def migration_mark_success(self,
|
|
247
|
+
def migration_mark_success(self, site: str, task_id: str) -> bool:
|
|
248
248
|
"""
|
|
249
249
|
标记迁移成功
|
|
250
250
|
"""
|
|
251
251
|
# 构建 API URL
|
|
252
|
-
|
|
253
|
-
url = f"{
|
|
252
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
253
|
+
url = f"{site_info.endpoint}/api/v1/dsyncer/secret_api/task/mark_migrate_success/"
|
|
254
254
|
|
|
255
255
|
# 准备请求头
|
|
256
|
-
headers = self._build_headers(
|
|
256
|
+
headers = self._build_headers(site, secret_api=True)
|
|
257
257
|
|
|
258
|
-
|
|
258
|
+
data_raw = {"task_id": task_id}
|
|
259
259
|
|
|
260
|
-
response_data = self._make_request("POST", url, headers,
|
|
260
|
+
response_data = self._make_request("POST", url, headers, data_raw)
|
|
261
261
|
|
|
262
|
-
logger.debug(f"migration_mark_success return {
|
|
262
|
+
logger.debug(f"migration_mark_success return {site} {task_id}, json_data: {response_data}")
|
|
263
263
|
success_task = response_data.get("data", {}).get("success_task", [])
|
|
264
264
|
return task_id in success_task
|
|
265
265
|
|
|
266
|
-
def migrate_task(self,
|
|
266
|
+
def migrate_task(self, site: str, task_id: str, app_parallel: int) -> Optional[str]:
|
|
267
267
|
"""
|
|
268
268
|
迁移任务到DFlow
|
|
269
269
|
|
|
@@ -271,13 +271,13 @@ class DSyncerHelper:
|
|
|
271
271
|
Optional[str]: 错误信息,成功时返回None,失败时返回错误信息
|
|
272
272
|
"""
|
|
273
273
|
# 构建 API URL
|
|
274
|
-
|
|
275
|
-
url = f"{
|
|
274
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
275
|
+
url = f"{site_info.endpoint}/api/v1/dsyncer/secret_api/task/migrate2dflow/single"
|
|
276
276
|
|
|
277
277
|
# 准备请求头
|
|
278
|
-
headers = self._build_headers(
|
|
278
|
+
headers = self._build_headers(site, secret_api=True)
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
data_raw = {
|
|
281
281
|
"task_id": task_id,
|
|
282
282
|
"delay_threshold": "20s",
|
|
283
283
|
"dsyncer_task_pause_threshold": "180s",
|
|
@@ -289,9 +289,9 @@ class DSyncerHelper:
|
|
|
289
289
|
"app_parallel": app_parallel,
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
response_data = self._make_request("POST", url, headers,
|
|
292
|
+
response_data = self._make_request("POST", url, headers, data_raw)
|
|
293
293
|
|
|
294
|
-
logger.debug(f"migrate_task return {
|
|
294
|
+
logger.debug(f"migrate_task return {site} {task_id}, json_data: {response_data}")
|
|
295
295
|
message = response_data.get("message")
|
|
296
296
|
if response_data.get("code") == 0 and message == "ok":
|
|
297
297
|
err_message = None
|
dtsdance/metrics_fe.py
CHANGED
|
@@ -2,7 +2,7 @@ from loguru import logger
|
|
|
2
2
|
import requests
|
|
3
3
|
import threading
|
|
4
4
|
import time
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import NamedTuple, Optional
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
7
|
|
|
8
8
|
|
|
@@ -60,9 +60,9 @@ def get_metric_type_by_name(metric_name: str) -> Optional[MetricType]:
|
|
|
60
60
|
return None
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
class
|
|
63
|
+
class MetricsClient:
|
|
64
64
|
"""
|
|
65
|
-
Metrics
|
|
65
|
+
Metrics Client
|
|
66
66
|
"""
|
|
67
67
|
|
|
68
68
|
# https://cloud.bytedance.net/docs/metrics/docs/63bbbb1ec6b537022a6000c7/63bbd4e8777a300220d4ac3a?x-resource-account=public&x-bc-region-id=bytedance
|
|
@@ -71,7 +71,7 @@ class MetricsHelper:
|
|
|
71
71
|
|
|
72
72
|
def __init__(self, envs: dict[str, MetricEnvInfo]):
|
|
73
73
|
"""
|
|
74
|
-
初始化 Metrics
|
|
74
|
+
初始化 Metrics Client
|
|
75
75
|
从配置文件加载所有环境的信息,并为每个环境初始化访问令牌
|
|
76
76
|
"""
|
|
77
77
|
self.envs = envs
|
|
@@ -80,7 +80,7 @@ class MetricsHelper:
|
|
|
80
80
|
self.token_lock = threading.Lock()
|
|
81
81
|
|
|
82
82
|
# 初始化访问令牌缓存,按环境名称索引
|
|
83
|
-
self.tokens:
|
|
83
|
+
self.tokens: dict[str, str] = {}
|
|
84
84
|
|
|
85
85
|
# 更新所有环境的配置信息
|
|
86
86
|
self._refresh_tokens()
|
|
@@ -212,8 +212,8 @@ class MetricsHelper:
|
|
|
212
212
|
return self.envs[env_name].endpoint
|
|
213
213
|
|
|
214
214
|
def _build_query_payload(
|
|
215
|
-
self, metric_name: str, aggregator: str, start_time_ms: int, end_time_ms: int, filters:
|
|
216
|
-
) ->
|
|
215
|
+
self, metric_name: str, aggregator: str, start_time_ms: int, end_time_ms: int, filters: list[dict], rate: bool, **kwargs
|
|
216
|
+
) -> dict:
|
|
217
217
|
"""
|
|
218
218
|
构建查询请求体
|
|
219
219
|
|
|
@@ -254,8 +254,8 @@ class MetricsHelper:
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
def _execute_metrics_query(
|
|
257
|
-
self, env: str, metric_name: str, aggregator: str, start_time: str, end_time: str, filters:
|
|
258
|
-
) ->
|
|
257
|
+
self, env: str, metric_name: str, aggregator: str, start_time: str, end_time: str, filters: list[dict], rate: bool, **kwargs
|
|
258
|
+
) -> dict:
|
|
259
259
|
"""
|
|
260
260
|
执行通用的指标查询
|
|
261
261
|
|
|
@@ -318,7 +318,7 @@ class MetricsHelper:
|
|
|
318
318
|
|
|
319
319
|
def get_mq_group_metrics(
|
|
320
320
|
self, env: str, cluster: str, topic: str, group: str, start_time: str, end_time: str, metric_type: MetricType, **kwargs
|
|
321
|
-
) ->
|
|
321
|
+
) -> dict:
|
|
322
322
|
"""
|
|
323
323
|
获取指定时间段的堆积指标
|
|
324
324
|
|
|
@@ -398,7 +398,7 @@ class MetricsHelper:
|
|
|
398
398
|
|
|
399
399
|
return int(max_consume_tps)
|
|
400
400
|
|
|
401
|
-
def get_dsyncer_task_metrics(self, env: str, metric_type: MetricType, dsyncer_increment_task_id: str, start_time: str, end_time: str) ->
|
|
401
|
+
def get_dsyncer_task_metrics(self, env: str, metric_type: MetricType, dsyncer_increment_task_id: str, start_time: str, end_time: str) -> dict:
|
|
402
402
|
"""
|
|
403
403
|
获取 DSyncer 任务指定时间段的指标
|
|
404
404
|
|
|
@@ -434,7 +434,7 @@ class MetricsHelper:
|
|
|
434
434
|
logger.error(f"get_task_latency_metrics env: {env}, dsyncer_increment_task_id: {dsyncer_increment_task_id}, error: {e}")
|
|
435
435
|
raise Exception(f"获取指标失败: {e}")
|
|
436
436
|
|
|
437
|
-
def get_dflow_task_metrics(self, env: str, dflow_task_id: str, metric_type: MetricType, start_time: str, end_time: str, **kwargs) ->
|
|
437
|
+
def get_dflow_task_metrics(self, env: str, dflow_task_id: str, metric_type: MetricType, start_time: str, end_time: str, **kwargs) -> dict:
|
|
438
438
|
"""
|
|
439
439
|
获取指定时间段的 DFlow 指标
|
|
440
440
|
|
|
@@ -471,7 +471,7 @@ class MetricsHelper:
|
|
|
471
471
|
raise Exception(f"获取指标失败: {e}")
|
|
472
472
|
|
|
473
473
|
@staticmethod
|
|
474
|
-
def _remove_last_datapoint(end_time: str, dps:
|
|
474
|
+
def _remove_last_datapoint(end_time: str, dps: dict[str, float]) -> dict[str, float]:
|
|
475
475
|
"""
|
|
476
476
|
移除监控数据中最后一个数据点,因为最后一个点往往不准确
|
|
477
477
|
只有当最后一个点的时间戳与 end_time 相差在 15 秒内时才移除
|
dtsdance/spacex.py
CHANGED
|
@@ -1,69 +1,68 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Any, NamedTuple
|
|
2
|
+
from .bytecloud import ByteCloudClient
|
|
2
3
|
import requests
|
|
3
|
-
import json
|
|
4
|
-
from typing import Dict, List
|
|
5
4
|
from loguru import logger
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
class
|
|
7
|
+
class GatewayInfo(NamedTuple):
|
|
8
|
+
mgr_env: str
|
|
9
|
+
ctrl_name: str
|
|
10
|
+
gateway_endpoint: str
|
|
11
|
+
auth_user: str
|
|
12
|
+
auth_password: str
|
|
13
|
+
root_secret_key: str
|
|
14
|
+
gw_meta_db: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SpaceXClient:
|
|
9
18
|
"""
|
|
10
19
|
SpaceX 通知服务客户端,用于发送飞书消息
|
|
11
20
|
"""
|
|
12
21
|
|
|
13
|
-
|
|
14
|
-
_SPACEX_DOMAIN = {
|
|
15
|
-
"BOE": "spacex-api-boe.byted.org",
|
|
16
|
-
"China-North": "spacex-api.byted.org",
|
|
17
|
-
"Singapore-Central": "spacex-api-i18n.byted.org",
|
|
18
|
-
"EUTTP": "spacex-api.tiktoke.org",
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
def __init__(self, bytecloud_helper: ByteCloudHelper):
|
|
22
|
+
def __init__(self, bytecloud_client: ByteCloudClient):
|
|
22
23
|
"""
|
|
23
24
|
初始化 SpaceX 通知客户端
|
|
24
25
|
"""
|
|
25
|
-
self.
|
|
26
|
+
self.bytecloud_client = bytecloud_client
|
|
26
27
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
def list_mgr(self, site: str) -> list[Any]:
|
|
29
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
30
|
+
url = f"{site_info.endpoint_bytedts_spacex}/bytedts/v1/queryServerMeta"
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
try:
|
|
33
|
+
response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site))
|
|
34
|
+
response.raise_for_status()
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
result = response.json()
|
|
37
|
+
return result.get("data", [])
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
logger.debug(f"send_feishu_message env: {env}, feishu_content: {str(feishu_content)[:100]}..., feishu_groups: {feishu_groups}")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.warning(f"do quest queryServerMeta exception: {e}")
|
|
41
|
+
raise
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
def register_gateway(self, site: str, gateway_info: GatewayInfo) -> bool:
|
|
44
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
45
|
+
url = f"{site_info.endpoint_bytedts_spacex}/bytedts/v1/registryGateway"
|
|
46
|
+
data_raw = {
|
|
47
|
+
"server_region": gateway_info.mgr_env,
|
|
48
|
+
"cluster_region": gateway_info.ctrl_name,
|
|
49
|
+
"cluster_name": gateway_info.ctrl_name,
|
|
50
|
+
"server_domain": gateway_info.gateway_endpoint,
|
|
51
|
+
"frontend_user": gateway_info.auth_user,
|
|
52
|
+
"gateway_user": gateway_info.auth_user,
|
|
53
|
+
"root_secret_key": gateway_info.root_secret_key,
|
|
54
|
+
"gateway_password": gateway_info.auth_password,
|
|
55
|
+
"frontend_password": gateway_info.auth_password,
|
|
56
|
+
"gw_meta_db": gateway_info.gw_meta_db,
|
|
57
|
+
"gateway_type": "psm",
|
|
52
58
|
}
|
|
53
|
-
|
|
54
|
-
# 发送 HTTP POST 请求
|
|
55
59
|
try:
|
|
56
|
-
url =
|
|
57
|
-
headers = {
|
|
58
|
-
"Content-Type": "application/json",
|
|
59
|
-
"X-Jwt-Token": self.bytecloud_helper.get_jwt_token(env),
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
response = requests.post(url, json=body, headers=headers)
|
|
60
|
+
response = requests.post(url, json=data_raw, headers=self.bytecloud_client.build_request_headers(site))
|
|
63
61
|
response.raise_for_status()
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
return
|
|
63
|
+
result = response.json()
|
|
64
|
+
return result.get("message", "") == "ok"
|
|
65
|
+
|
|
67
66
|
except Exception as e:
|
|
68
|
-
|
|
67
|
+
logger.warning(f"do quest registryGateway exception: {e}")
|
|
69
68
|
raise
|
dtsdance/tcc_inner.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""TCC (Toutiao Config Center) API client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from dtsdance.bytecloud import ByteCloudClient
|
|
7
|
+
from dtsdance.tcc_open import TCCError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TCCInnerClient:
|
|
11
|
+
"""Client for TCC OpenAPI."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, bytecloud_client: ByteCloudClient) -> None:
|
|
14
|
+
self.bytecloud_client = bytecloud_client
|
|
15
|
+
|
|
16
|
+
def list_configs(
|
|
17
|
+
self,
|
|
18
|
+
site: str,
|
|
19
|
+
ns_name: str,
|
|
20
|
+
region: str,
|
|
21
|
+
dir: str,
|
|
22
|
+
conf_name: str,
|
|
23
|
+
) -> dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
List TCC configurations.
|
|
26
|
+
"""
|
|
27
|
+
site_info = self.bytecloud_client.get_site_info(site)
|
|
28
|
+
url = f"{site_info.endpoint}/api/v3/tcc/bcc/config/list_v2"
|
|
29
|
+
|
|
30
|
+
data_raw = {
|
|
31
|
+
"ns_name": ns_name,
|
|
32
|
+
"region": region,
|
|
33
|
+
"dir_path": dir,
|
|
34
|
+
"keyword": conf_name,
|
|
35
|
+
"env": "prod",
|
|
36
|
+
"scope": "all",
|
|
37
|
+
"condition": "name",
|
|
38
|
+
"pn": 1,
|
|
39
|
+
"rn": 100,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site), json=data_raw, timeout=30)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
result = response.json()
|
|
46
|
+
|
|
47
|
+
# Check for errors in response
|
|
48
|
+
base_resp = result.get("base_resp", {})
|
|
49
|
+
if base_resp.get("error_code", 0) != 0:
|
|
50
|
+
raise TCCError(f"TCC API error: {base_resp.get('error_message', 'Unknown error')}")
|
|
51
|
+
|
|
52
|
+
return result.get("data", {})
|
|
53
|
+
|
|
54
|
+
except requests.RequestException as e:
|
|
55
|
+
raise TCCError(f"Failed to get TCC config: {str(e)}") from e
|
dtsdance/{tcc.py → tcc_open.py}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""TCC (Toutiao Config Center) API client."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Any
|
|
@@ -16,19 +16,24 @@ class TCCConfigItem:
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class TCCError(Exception):
|
|
19
|
-
|
|
19
|
+
pass
|
|
20
|
+
|
|
20
21
|
|
|
22
|
+
class TCCItemNotFound(Exception):
|
|
21
23
|
pass
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class TCCClient:
|
|
25
27
|
"""Client for TCC OpenAPI."""
|
|
26
28
|
|
|
27
|
-
def __init__(self, svc_account: str, svc_secret: str,
|
|
29
|
+
def __init__(self, svc_account: str, svc_secret: str, endpoint: str):
|
|
28
30
|
self.svc_account = svc_account
|
|
29
|
-
self.
|
|
30
|
-
self.
|
|
31
|
-
|
|
31
|
+
self.svc_secret = svc_secret
|
|
32
|
+
self.endpoint = endpoint
|
|
33
|
+
|
|
34
|
+
def _build_headers(self) -> dict[str, str]:
|
|
35
|
+
return {
|
|
36
|
+
"Authorization": f"Bearer {self.svc_secret}",
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
def publish_config(
|
|
@@ -50,7 +55,7 @@ class TCCClient:
|
|
|
50
55
|
Raises:
|
|
51
56
|
TCCError: If API call fails
|
|
52
57
|
"""
|
|
53
|
-
url = f"{self.
|
|
58
|
+
url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/update"
|
|
54
59
|
|
|
55
60
|
payload = {
|
|
56
61
|
"ns_name": ns_name,
|
|
@@ -67,7 +72,7 @@ class TCCClient:
|
|
|
67
72
|
|
|
68
73
|
try:
|
|
69
74
|
# print(f"writing tcc config, payload: {payload}")
|
|
70
|
-
response = requests.post(url, headers=self.
|
|
75
|
+
response = requests.post(url, headers=self._build_headers(), json=payload, timeout=30)
|
|
71
76
|
if response.status_code != 200:
|
|
72
77
|
raise TCCError(f"TCC API request failed with status {response.status_code}\n" f"Response: {response.text}")
|
|
73
78
|
|
|
@@ -108,7 +113,7 @@ class TCCClient:
|
|
|
108
113
|
Raises:
|
|
109
114
|
TCCError: If API call fails
|
|
110
115
|
"""
|
|
111
|
-
url = f"{self.
|
|
116
|
+
url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/list"
|
|
112
117
|
|
|
113
118
|
params = {
|
|
114
119
|
"ns_name": ns_name,
|
|
@@ -120,7 +125,7 @@ class TCCClient:
|
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
try:
|
|
123
|
-
response = requests.post(url, headers=self.
|
|
128
|
+
response = requests.post(url, headers=self._build_headers(), params=params, timeout=30)
|
|
124
129
|
response.raise_for_status()
|
|
125
130
|
result = response.json()
|
|
126
131
|
|
|
@@ -191,8 +196,8 @@ class TCCClient:
|
|
|
191
196
|
self,
|
|
192
197
|
ns_name: str,
|
|
193
198
|
region: str,
|
|
194
|
-
conf_name: str,
|
|
195
199
|
dir: str,
|
|
200
|
+
conf_name: str,
|
|
196
201
|
) -> dict[str, Any]:
|
|
197
202
|
"""
|
|
198
203
|
Get TCC configuration.
|
|
@@ -209,7 +214,7 @@ class TCCClient:
|
|
|
209
214
|
Raises:
|
|
210
215
|
TCCError: If API call fails
|
|
211
216
|
"""
|
|
212
|
-
url = f"{self.
|
|
217
|
+
url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/get"
|
|
213
218
|
|
|
214
219
|
params = {
|
|
215
220
|
"ns_name": ns_name,
|
|
@@ -219,14 +224,18 @@ class TCCClient:
|
|
|
219
224
|
}
|
|
220
225
|
|
|
221
226
|
try:
|
|
222
|
-
response = requests.get(url, headers=self.
|
|
227
|
+
response = requests.get(url, headers=self._build_headers(), params=params, timeout=30)
|
|
223
228
|
response.raise_for_status()
|
|
224
229
|
result = response.json()
|
|
225
230
|
|
|
226
231
|
# Check for errors in response
|
|
227
232
|
base_resp = result.get("base_resp", {})
|
|
228
233
|
if base_resp.get("error_code", 0) != 0:
|
|
229
|
-
|
|
234
|
+
error_message = base_resp.get("error_message", "Unknown error")
|
|
235
|
+
if "reason:RESOURCE_NOT_FOUND" in error_message:
|
|
236
|
+
raise TCCItemNotFound(f"TCC Item not found error: {error_message}")
|
|
237
|
+
|
|
238
|
+
raise TCCError(f"TCC API error: {error_message}")
|
|
230
239
|
|
|
231
240
|
return result.get("data", {})
|
|
232
241
|
|
dts_dance-0.1.9.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
dtsdance/__init__.py,sha256=Yl_jEZ5weYfcrklnDvwB4wSgCOvMBLRRgWx0gHs3qfM,49
|
|
2
|
-
dtsdance/bytecloud.py,sha256=joixJEQxX9k3VqFDNrccTVSI2WVdxdZ1JyuWFVF9zjo,4998
|
|
3
|
-
dtsdance/dflow.py,sha256=gow-zCX5lbQqqW2MRWvWynej7pZ_TQ5NmOb__j2aQT4,5497
|
|
4
|
-
dtsdance/dsyncer.py,sha256=1OateZLGekYP_EF9_oAHqsj4sdQm6ahIoRllEgT9Y3I,11142
|
|
5
|
-
dtsdance/feishu_base.py,sha256=2j4ZM4PFqJ-9EhC6DQ1OmAg--3VBGZyyRuxyjL0j6OU,3733
|
|
6
|
-
dtsdance/feishu_table.py,sha256=ZUeoKrM4nmm5hFhc3vWOVYeLP390orm-284Od92G4iQ,8424
|
|
7
|
-
dtsdance/metrics_fe.py,sha256=uPdbjGaaYBOv-rK7lKUx6aVT5Sj6ZkW9m06NyqMrPf0,18990
|
|
8
|
-
dtsdance/s3.py,sha256=Bh-cwLksfO5PewNtIzE_Md3rRLDLI1DUVoOD7Pou5T8,1294
|
|
9
|
-
dtsdance/spacex.py,sha256=wgbuwDTLXopJnLn2puX-9MfeVXvi_nQ3C9uN8McoJf4,2113
|
|
10
|
-
dtsdance/tcc.py,sha256=M_0cOVYyvUgjnC1uKWdz3YgRplafQUqX39gX_YEg8eE,6563
|
|
11
|
-
dts_dance-0.1.9.dist-info/METADATA,sha256=IbvejpKLh-EM12cf9XtlwCVkUbD5jjvRc_Pz7PA_5b8,793
|
|
12
|
-
dts_dance-0.1.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
-
dts_dance-0.1.9.dist-info/RECORD,,
|
|
File without changes
|