dts-dance 0.1.9__py3-none-any.whl → 0.2.0__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dts-dance
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: dts dance lib
5
5
  Keywords: observation,tools
6
6
  Requires-Python: >=3.12
@@ -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=WtD3AbDgGRraddJIxzZs8Bd6Ts56XtqJcjb0UrhnWFQ,5097
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=lVhDfGzFZsfeYhWGT-8AE3JM5ZksJdBWRQ-GRL0ymqY,2342
10
+ dtsdance/tcc_inner.py,sha256=xy3N1N3BNl1oc2oMNHKhnsj47rAKOOJ-aXw8GtA2De8,1621
11
+ dtsdance/tcc_open.py,sha256=wjUh56_CDb15s0qXw61fb5JehgIVcySUf0hTaF7V92Y,6674
12
+ dts_dance-0.2.0.dist-info/METADATA,sha256=7IBiJk2ubNRRx2p6gjWnSxqYXa1PzQ_HLTf3DFwSkCI,793
13
+ dts_dance-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
+ dts_dance-0.2.0.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 ByteCloudEnvInfo(NamedTuple):
9
+ class SiteConfig(NamedTuple):
10
10
  name: str
11
11
  endpoint: str
12
- token: str
12
+ svc_account: str
13
+ svc_secret: str
14
+ endpoint_bytedts_spacex: str | None = None
13
15
 
14
16
 
15
- class ByteCloudHelper:
17
+ class ByteCloudClient:
16
18
  """
17
- ByteCloud Helper
19
+ ByteCloud Client
18
20
  """
19
21
 
20
22
  # 每小时刷新一次,单位为秒
21
23
  _REFRESH_INTERVAL = 1 * 60 * 60
22
24
 
23
- def __init__(self, envs: dict[str, ByteCloudEnvInfo]):
25
+ def __init__(self, sites: dict[str, SiteConfig]):
24
26
  """
25
- 初始化 ByteCloud Helper
27
+ 初始化 ByteCloud Client
26
28
  从配置文件加载所有环境的信息,并为每个环境初始化 JWT 令牌
27
- envs 中保存内容 list[(name, endpoint, token)]
29
+ sites 中保存内容 list[(name, endpoint, svc_account, svc_secret)]
28
30
  """
29
- self.envs = envs
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 _, env in self.envs.items():
71
+ for _, site in self.sites.items():
70
72
  try:
71
73
  # 刷新令牌
72
- new_token = self._acquire_jwt_token(env.endpoint, env.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[env.name] = new_token
76
- logger.debug(f"环境 {env.name} 的 JWT 令牌成功刷新,新令牌: {new_token}")
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"环境 {env.name} 的 JWT 令牌刷新失败: {e}")
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, token: str) -> 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 " + token,
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, env: str) -> str:
130
+ def get_jwt_token(self, site: str) -> str:
129
131
  """
130
- 获取指定环境的 JWT 令牌
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
- env: 环境名称,如 'China-North', 'China-East', 'Singapore-Central' 等
145
+ site: 站点名称
134
146
 
135
147
  Returns:
136
- str: 当前有效的 JWT 令牌
137
-
138
- Raises:
139
- KeyError: 如果指定的环境不存在
148
+ dict[str, str]: 请求头字典
140
149
  """
141
- # 使用线程锁保护并发访问
142
- with self.token_lock:
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 get_env_info(self, env: str) -> ByteCloudEnvInfo:
153
+ def get_site_info(self, site: str) -> SiteConfig:
148
154
  """
149
155
  获取指定环境的信息
150
156
 
151
157
  Args:
152
- env: 环境名称
158
+ site: 站点名称
153
159
 
154
160
  Returns:
155
- ByteCloudEnvInfo: 指定环境的信息
161
+ SiteConfig: 指定站点的信息
156
162
 
157
163
  Raises:
158
- KeyError: 如果指定的环境不存在
164
+ KeyError: 如果指定的站点不存在
159
165
  """
160
- if env not in self.envs:
161
- raise KeyError(f"环境 {env} 不存在")
166
+ if site not in self.sites:
167
+ raise KeyError(f"站点 {site} 不存在")
162
168
 
163
- return self.envs[env]
169
+ return self.sites[site]
dtsdance/dflow.py CHANGED
@@ -1,27 +1,13 @@
1
1
  from typing import Any, cast, Optional
2
2
  from loguru import logger
3
- from .bytecloud import ByteCloudHelper
3
+ from .bytecloud import ByteCloudClient
4
4
  import requests
5
5
 
6
6
 
7
- class DFlowHelper:
7
+ class DFlowClient:
8
8
 
9
- def __init__(self, bytecloud_helper: ByteCloudHelper) -> None:
10
- self.bytecloud_helper = bytecloud_helper
11
-
12
- def _build_headers(self, env: str) -> dict[str, str]:
13
- """
14
- 构建请求头
15
-
16
- Args:
17
- env: 环境名称
18
-
19
- Returns:
20
- dict[str, str]: 请求头字典
21
- """
22
- jwt_token = self.bytecloud_helper.get_jwt_token(env)
23
- headers = {"x-jwt-token": jwt_token}
24
- return headers
9
+ def __init__(self, bytecloud_client: ByteCloudClient) -> None:
10
+ self.bytecloud_client = bytecloud_client
25
11
 
26
12
  def _make_request(self, method: str, url: str, headers: dict[str, str], json_data: Optional[dict] = None) -> dict[str, Any]:
27
13
  """
@@ -58,30 +44,27 @@ class DFlowHelper:
58
44
  logger.warning(error_msg)
59
45
  raise
60
46
 
61
- def get_dflow_info(self, env: str, task_id: str) -> dict[str, Any]:
47
+ def get_dflow_info(self, site: str, task_id: str) -> dict[str, Any]:
62
48
  """
63
49
  获取 DFlow 任务信息
64
50
 
65
51
  Args:
66
- env: 环境名称
52
+ site: 站点名称
67
53
  task_id: DFlow 任务 ID
68
54
 
69
55
  Returns:
70
56
  dict[str, Any]: DFlow 任务信息,包含 create_time 等字段
71
57
  """
72
58
  # 构建 API URL
73
- env_info = self.bytecloud_helper.get_env_info(env)
74
- url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeTaskInfo"
75
-
76
- # 准备请求头
77
- headers = self._build_headers(env)
59
+ site_info = self.bytecloud_client.get_site_info(site)
60
+ url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeTaskInfo"
78
61
 
79
62
  # 构建请求数据
80
63
  json_data = {"id": int(task_id)}
81
64
 
82
- response_data = self._make_request("POST", url, headers, json_data)
65
+ response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
83
66
 
84
- logger.info(f"get_dflow_info {env} {task_id}, message: {response_data.get('message')}")
67
+ logger.info(f"get_dflow_info {site} {task_id}, message: {response_data.get('message')}")
85
68
 
86
69
  try:
87
70
  data = cast(dict, response_data.get("data", {}))
@@ -99,71 +82,65 @@ class DFlowHelper:
99
82
  except (KeyError, AttributeError, Exception) as e:
100
83
  raise Exception(f"无法从响应中提取 DFlow 任务信息数据: {str(e)}")
101
84
 
102
- def generate_task_url(self, env: str, task_id: str) -> str:
85
+ def generate_task_url(self, site: str, task_id: str) -> str:
103
86
  """
104
87
  获取 DFlow 任务详情页面的 URL
105
88
 
106
89
  Args:
107
- env: 环境名称
90
+ site: 站点名称
108
91
  task_id: DFlow 任务 ID
109
92
 
110
93
  Returns:
111
94
  str: DFlow 任务详情页面的 URL
112
95
  """
113
96
  # 根据环境生成对应的 scope 参数
114
- env_info = self.bytecloud_helper.get_env_info(env)
115
- return f"{env_info.endpoint}/bytedts/datasync/detail/{task_id}?scope={env}"
97
+ site_info = self.bytecloud_client.get_site_info(site)
98
+ return f"{site_info.endpoint}/bytedts/datasync/detail/{task_id}?scope={site}"
116
99
 
117
- def init_resources(self, env: str, ctrl_env: str) -> bool:
100
+ def init_resources(self, site: str, ctrl_env: str) -> bool:
118
101
  """
119
102
  初始化 CTRL 环境资源
120
103
 
121
104
  Args:
122
- env: 环境名称
105
+ site: 站点名称
123
106
  ctrl_env: 控制环境
124
107
 
125
108
  Returns:
126
109
  bool: CTRL 环境资源初始化结果
127
110
  """
128
111
  # 构建 API URL
129
- env_info = self.bytecloud_helper.get_env_info(env)
130
- url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/InitSystemResource"
131
-
132
- # 准备请求头
133
- headers = self._build_headers(env)
112
+ site_info = self.bytecloud_client.get_site_info(site)
113
+ url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/InitSystemResource"
134
114
 
135
115
  # 构建请求数据
136
116
  json_data = {"ctrl_env": ctrl_env}
137
117
 
138
- response_data = self._make_request("POST", url, headers, json_data)
118
+ response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
139
119
 
140
120
  message = response_data.get("message")
141
- logger.info(f"int_resources {env} {ctrl_env}, message: {message}")
121
+ logger.info(f"int_resources {site} {ctrl_env}, message: {message}")
142
122
 
143
123
  return message == "ok"
144
124
 
145
- def list_resources(self, env: str, ctrl_env: str) -> list[str]:
125
+ def list_resources(self, site: str, ctrl_env: str) -> list[str]:
146
126
  """
147
127
  列举 CTRL 环境资源列表
148
128
 
149
129
  Args:
150
- env: 环境名称
130
+ site: 站点名称
151
131
  ctrl_env: 控制环境
152
132
 
153
133
  Returns:
154
134
  list[str]: CTRL 环境资源列表
155
135
  """
156
136
  # 构建 API URL
157
- env_info = self.bytecloud_helper.get_env_info(env)
158
- url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
159
-
160
- # 准备请求头
161
- headers = self._build_headers(env)
137
+ site_info = self.bytecloud_client.get_site_info(site)
138
+ url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
162
139
 
163
140
  # 构建请求数据
164
141
  json_data = {"offset": 0, "limit": 10, "ctrl_env": ctrl_env}
165
142
 
166
- response_data = self._make_request("POST", url, headers, json_data)
143
+ response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
167
144
 
168
145
  try:
169
146
  data = cast(dict, response_data.get("data", {}))
dtsdance/dsyncer.py CHANGED
@@ -1,4 +1,4 @@
1
- from .bytecloud import ByteCloudHelper
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 DSyncerHelper:
18
+ class DSyncerClient:
19
19
 
20
- def __init__(self, envs: dict[str, DSyncerEnvInfo], bytecloud_helper: ByteCloudHelper) -> None:
20
+ def __init__(self, envs: dict[str, DSyncerEnvInfo], bytecloud_client: ByteCloudClient) -> None:
21
21
  self.envs = envs
22
- self.bytecloud_helper = bytecloud_helper
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.bytecloud_helper.get_jwt_token(env)
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], json_data: Optional[dict] = None) -> dict[str, Any]:
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=json_data, headers=headers)
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, env: str, task_id: str) -> dict[str, str]:
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
- env: 环境名称
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
- env_info = self.bytecloud_helper.get_env_info(env)
93
- url = f"{env_info.endpoint}/api/v1/dsyncer/openapi/taskinfo/{task_id}/"
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(env)
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, env: str, task_id: str) -> tuple[str, str]:
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
- env_info = self.bytecloud_helper.get_env_info(env)
106
- url = f"{env_info.endpoint}/api/v1/dsyncer/openapi/taskinfo/{task_id}/migrate/"
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(env)
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 {env} {task_id}, message: {message}")
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, env: str, task_id: str) -> str:
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
- env_info = self.bytecloud_helper.get_env_info(env)
148
- return DSyncer_Task_Detail_URL.format(endpoint=env_info.endpoint, task_id=task_id)
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, env: str, task_id: str) -> Tuple[bool, str]:
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
- env_info = self.bytecloud_helper.get_env_info(env)
212
- url = f"{env_info.endpoint}/api/v1/dsyncer/secret_api/task/migrate/check"
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(env, secret_api=True)
215
+ headers = self._build_headers(site, secret_api=True)
216
216
 
217
- json_data = {"task_id": task_id}
217
+ data_raw = {"task_id": task_id}
218
218
 
219
- response_data = self._make_request("POST", url, headers, json_data)
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 {env} {task_id}, message: {message}")
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, env: str, task_id: str) -> bool:
228
+ def migration_rollback(self, site: str, task_id: str) -> bool:
229
229
  """
230
230
  执行回滚
231
231
  """
232
232
  # 构建 API URL
233
- env_info = self.bytecloud_helper.get_env_info(env)
234
- url = f"{env_info.endpoint}/api/v1/dsyncer/secret_api/task/rollback_migrate2dsyncer/"
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(env, secret_api=True)
237
+ headers = self._build_headers(site, secret_api=True)
238
238
 
239
- json_data = {"task_id_list": [task_id]}
239
+ data_raw = {"task_id_list": [task_id]}
240
240
 
241
- response_data = self._make_request("POST", url, headers, json_data)
241
+ response_data = self._make_request("POST", url, headers, data_raw)
242
242
 
243
- logger.debug(f"migration_mark_rollback return {env} {task_id}, json_data: {response_data}")
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, env: str, task_id: str) -> bool:
247
+ def migration_mark_success(self, site: str, task_id: str) -> bool:
248
248
  """
249
249
  标记迁移成功
250
250
  """
251
251
  # 构建 API URL
252
- env_info = self.bytecloud_helper.get_env_info(env)
253
- url = f"{env_info.endpoint}/api/v1/dsyncer/secret_api/task/mark_migrate_success/"
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(env, secret_api=True)
256
+ headers = self._build_headers(site, secret_api=True)
257
257
 
258
- json_data = {"task_id": task_id}
258
+ data_raw = {"task_id": task_id}
259
259
 
260
- response_data = self._make_request("POST", url, headers, json_data)
260
+ response_data = self._make_request("POST", url, headers, data_raw)
261
261
 
262
- logger.debug(f"migration_mark_success return {env} {task_id}, json_data: {response_data}")
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, env: str, task_id: str, app_parallel: int) -> Optional[str]:
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
- env_info = self.bytecloud_helper.get_env_info(env)
275
- url = f"{env_info.endpoint}/api/v1/dsyncer/secret_api/task/migrate2dflow/single"
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(env, secret_api=True)
278
+ headers = self._build_headers(site, secret_api=True)
279
279
 
280
- json_data = {
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, json_data)
292
+ response_data = self._make_request("POST", url, headers, data_raw)
293
293
 
294
- logger.debug(f"migrate_task return {env} {task_id}, json_data: {response_data}")
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 Dict, List, NamedTuple, Optional
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 MetricsHelper:
63
+ class MetricsClient:
64
64
  """
65
- Metrics Helper
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 Helper
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: Dict[str, str] = {}
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: List[Dict], rate: bool, **kwargs
216
- ) -> Dict:
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: List[Dict], rate: bool, **kwargs
258
- ) -> Dict:
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
- ) -> Dict:
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) -> Dict:
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) -> Dict:
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: Dict[str, float]) -> Dict[str, float]:
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 .bytecloud import ByteCloudHelper
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 SpaceXNotifier:
7
+ class GatewayInfo(NamedTuple):
8
+ mgr_name: 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
- # 飞书机器人 webhook URL
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.bytecloud_helper = bytecloud_helper
26
+ self.bytecloud_client = bytecloud_client
26
27
 
27
- def send_feishu_message(self, env: str, feishu_content: Dict, feishu_groups: List[str]) -> str:
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
- Args:
32
- feishu_content: 飞书消息内容,包含卡片配置
33
- feishu_groups: 飞书群组 ID 列表
32
+ try:
33
+ response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site))
34
+ response.raise_for_status()
34
35
 
35
- Returns:
36
- str: 响应内容
36
+ result = response.json()
37
+ return result.get("data", [])
37
38
 
38
- Raises:
39
- Exception: 请求失败时抛出异常
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
- body = {
44
- "product_name": "ByteDTS-Inspector",
45
- "notification_detail": [
46
- {
47
- "channel": "Lark",
48
- "content": json.dumps(feishu_content),
49
- "lark_groups": feishu_groups,
50
- }
51
- ],
43
+ def registry_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_name,
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 = f"https://{self._SPACEX_DOMAIN[env]}/notification_service/api/v1/messages"
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
- print("response: {}".format(response.text))
66
- return response.text
63
+ result = response.json()
64
+ return result.get("message", "") == "ok"
65
+
67
66
  except Exception as e:
68
- print("发送消息时出错: {}".format(e))
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
@@ -1,4 +1,4 @@
1
- """TCE (Toutiao Cloud Engine) API client."""
1
+ """TCC (Toutiao Config Center) API client."""
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from typing import Any
@@ -24,11 +24,14 @@ class TCCError(Exception):
24
24
  class TCCClient:
25
25
  """Client for TCC OpenAPI."""
26
26
 
27
- def __init__(self, svc_account: str, svc_secret: str, base_url: str):
27
+ def __init__(self, svc_account: str, svc_secret: str, endpoint: str):
28
28
  self.svc_account = svc_account
29
- self.base_url = base_url
30
- self.headers = {
31
- "Authorization": f"Bearer {svc_secret}",
29
+ self.svc_secret = svc_secret
30
+ self.endpoint = endpoint
31
+
32
+ def _build_headers(self) -> dict[str, str]:
33
+ return {
34
+ "Authorization": f"Bearer {self.svc_secret}",
32
35
  }
33
36
 
34
37
  def publish_config(
@@ -50,7 +53,7 @@ class TCCClient:
50
53
  Raises:
51
54
  TCCError: If API call fails
52
55
  """
53
- url = f"{self.base_url}/api/v1/tcc_v3_openapi/bcc/open/config/update"
56
+ url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/update"
54
57
 
55
58
  payload = {
56
59
  "ns_name": ns_name,
@@ -67,7 +70,7 @@ class TCCClient:
67
70
 
68
71
  try:
69
72
  # print(f"writing tcc config, payload: {payload}")
70
- response = requests.post(url, headers=self.headers, json=payload, timeout=30)
73
+ response = requests.post(url, headers=self._build_headers(), json=payload, timeout=30)
71
74
  if response.status_code != 200:
72
75
  raise TCCError(f"TCC API request failed with status {response.status_code}\n" f"Response: {response.text}")
73
76
 
@@ -108,7 +111,7 @@ class TCCClient:
108
111
  Raises:
109
112
  TCCError: If API call fails
110
113
  """
111
- url = f"{self.base_url}/api/v1/tcc_v3_openapi/bcc/open/config/list"
114
+ url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/list"
112
115
 
113
116
  params = {
114
117
  "ns_name": ns_name,
@@ -120,7 +123,7 @@ class TCCClient:
120
123
  }
121
124
 
122
125
  try:
123
- response = requests.post(url, headers=self.headers, params=params, timeout=30)
126
+ response = requests.post(url, headers=self._build_headers(), params=params, timeout=30)
124
127
  response.raise_for_status()
125
128
  result = response.json()
126
129
 
@@ -191,8 +194,8 @@ class TCCClient:
191
194
  self,
192
195
  ns_name: str,
193
196
  region: str,
194
- conf_name: str,
195
197
  dir: str,
198
+ conf_name: str,
196
199
  ) -> dict[str, Any]:
197
200
  """
198
201
  Get TCC configuration.
@@ -209,7 +212,7 @@ class TCCClient:
209
212
  Raises:
210
213
  TCCError: If API call fails
211
214
  """
212
- url = f"{self.base_url}/api/v1/tcc_v3_openapi/bcc/open/config/get"
215
+ url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/get"
213
216
 
214
217
  params = {
215
218
  "ns_name": ns_name,
@@ -219,7 +222,7 @@ class TCCClient:
219
222
  }
220
223
 
221
224
  try:
222
- response = requests.get(url, headers=self.headers, params=params, timeout=30)
225
+ response = requests.get(url, headers=self._build_headers(), params=params, timeout=30)
223
226
  response.raise_for_status()
224
227
  result = response.json()
225
228
 
@@ -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,,