Skip to content

Commit 7759b5d

Browse files
Merge branch 'master' into features/CONTRIBUTING-20251020
2 parents 2745236 + cab9785 commit 7759b5d

File tree

3 files changed

+398
-102
lines changed

3 files changed

+398
-102
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ https://github.com/user-attachments/assets/9e48cac3-0af1-424c-9f16-3862d047cc68
3434
- 集群资源运维任务查询 (Later)
3535

3636
**Kubernetes 原生操作** (`ack_kubectl`)
37-
- 【注意⚠️】`ack_kubectl`会在`~/.kube`下生成和管理临时 kubeconfig,并在工具内部自动切换至正确上下文;它不会修改全局默认上下文或用户的`KUBECONFIG`。为避免与其他入口(如 shell 中的 `kubectl`)产生集群不一致,请在访问 ACK 集群时仅使用该工具,不要从外部直接执行 `kubectl`
3837
- 执行 `kubectl` 类操作(读写权限可控)
3938
- 获取日志、事件,资源的增删改查
4039
- 支持所有标准 Kubernetes API
@@ -142,8 +141,8 @@ https://github.com/user-attachments/assets/9e48cac3-0af1-424c-9f16-3862d047cc68
142141

143142
```bash
144143
# 克隆代码仓库
145-
git clone https://github.com/aliyun/alibabacloud-ack-mcp-server
146-
cd alibabacloud-ack-mcp-server
144+
git clone https://github.com/aliyun/alibabacloud-cs-mcp-server
145+
cd alibabacloud-cs-mcp-server
147146

148147
# 使用 Helm 部署
149148
helm install \
@@ -204,8 +203,8 @@ make build-binary
204203

205204
```bash
206205
# 克隆项目
207-
git clone https://github.com/aliyun/alibabacloud-ack-mcp-server
208-
cd alibabacloud-ack-mcp-server
206+
git clone https://github.com/aliyun/alibabacloud-cs-mcp-server
207+
cd alibabacloud-cs-mcp-server
209208

210209
# 安装依赖
211210
uv sync
@@ -313,7 +312,8 @@ make test
313312

314313
#### 🤝 如何贡献
315314

316-
1. **问题反馈**: 通过 [GitHub Issues](https://github.com/aliyun/alibabacloud-ack-mcp-server/issues)
315+
1. **问题反馈**: 通过 [GitHub Issues](https://github.com/aliyun/alibabacloud-cs-mcp-server/issues)
316+
2. **功能请求**: 通过 [Discussions](https://github.com/aliyun/alibabacloud-cs-mcp-server/discussions)
317317
3. **代码贡献**: Fork → 功能分支 → Pull Request
318318
4. **文档改进**: API 文档、教程编写
319319

src/ack_controlplane_log_handler.py

Lines changed: 89 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -44,70 +44,51 @@ def _get_cs_client(ctx: Context, region_id: str):
4444
return cs_client_factory(region_id, config)
4545

4646

47-
def _parse_time(time_str: str) -> int:
48-
"""解析时间字符串为 Unix 时间戳(秒级)。
49-
50-
支持格式:
51-
- ISO 8601: "2025-09-16T08:09:44Z" 或 "2025-09-16T08:09:44+08:00"
52-
- 相对时间: "30m", "1h", "24h", "7d"
53-
- Unix 时间戳: "1758037055" (秒级) 或 "1758037055000" (毫秒级,自动转换为秒级)
47+
def _parse_single_time(time_str: Optional[str], default_hours: int = 24) -> datetime:
48+
"""参考 ack_audit_log_handler 的实现:
49+
支持相对时间后缀(s/m/h/d/w)与 ISO 8601(允许 Z),返回 datetime。
50+
兼容纯数字的 unix 秒/毫秒。
5451
"""
55-
if not time_str:
56-
return int(datetime.now().timestamp())
57-
58-
# 检查是否是纯数字(Unix 时间戳)
59-
if time_str.isdigit():
60-
timestamp = int(time_str)
61-
# 如果是毫秒级时间戳(13位数字),转换为秒级
62-
if timestamp > 1e12: # 大于 2001-09-09 的时间戳,可能是毫秒级
63-
return timestamp // 1000
64-
else:
65-
return timestamp
66-
67-
# 处理相对时间格式
68-
relative_pattern = r'^(\d+)([smhd])$'
69-
match = re.match(relative_pattern, time_str.lower())
70-
if match:
71-
value = int(match.group(1))
72-
unit = match.group(2)
73-
now = datetime.now()
74-
75-
if unit == 's': # 秒
76-
delta = timedelta(seconds=value)
77-
elif unit == 'm': # 分钟
78-
delta = timedelta(minutes=value)
79-
elif unit == 'h': # 小时
80-
delta = timedelta(hours=value)
81-
elif unit == 'd': # 天
82-
delta = timedelta(days=value)
83-
else:
84-
raise ValueError(f"Unsupported time unit: {unit}")
52+
from datetime import datetime
8553

86-
return int((now - delta).timestamp())
54+
if not time_str:
55+
return datetime.now() - timedelta(hours=default_hours)
56+
57+
ts = str(time_str).strip()
58+
if ts.isdigit():
59+
iv = int(ts)
60+
if iv > 1e12: # 毫秒
61+
iv //= 1000
62+
return datetime.fromtimestamp(iv)
63+
64+
ts_lower = ts.lower()
65+
if ts_lower.endswith('h'):
66+
return datetime.now() - timedelta(hours=int(ts_lower[:-1]))
67+
if ts_lower.endswith('d'):
68+
return datetime.now() - timedelta(days=int(ts_lower[:-1]))
69+
if ts_lower.endswith('m'):
70+
return datetime.now() - timedelta(minutes=int(ts_lower[:-1]))
71+
if ts_lower.endswith('s'):
72+
return datetime.now() - timedelta(seconds=int(ts_lower[:-1]))
73+
if ts_lower.endswith('w'):
74+
return datetime.now() - timedelta(weeks=int(ts_lower[:-1]))
8775

88-
# ISO 8601 格式
8976
try:
90-
# 检查是否包含时区信息
91-
if not ('Z' in time_str or '+' in time_str or time_str.count('-') > 2):
92-
raise ValueError("ISO 8601 format must include timezone information (Z, +HH:MM, or -HH:MM)")
93-
94-
# 处理 Z 后缀(UTC时间)
95-
if time_str.endswith('Z'):
96-
time_str = time_str[:-1] + '+00:00'
97-
98-
dt = datetime.fromisoformat(time_str)
99-
# 确保转换为UTC时间戳
100-
if dt.tzinfo is None:
101-
# 如果没有时区信息,假设为UTC
102-
dt = dt.replace(tzinfo=None)
103-
return int(dt.timestamp())
104-
else:
105-
return int(dt.timestamp())
106-
except ValueError as e:
107-
if "timezone information" in str(e):
108-
raise e
109-
raise ValueError(
110-
f"Invalid time format: {time_str}. Expected ISO 8601 format (e.g., 2025-09-16T08:09:44Z), relative time (e.g., 24h), or Unix timestamp.")
77+
iso_str = ts
78+
if iso_str.endswith('Z'):
79+
iso_str = iso_str.replace('Z', '+00:00')
80+
return datetime.fromisoformat(iso_str)
81+
except ValueError:
82+
return datetime.now() - timedelta(hours=default_hours)
83+
84+
85+
def _parse_time_params(start_time: Optional[str], end_time: Optional[str]) -> tuple[int, int]:
86+
"""与审计日志对齐:返回秒级 unix,确保开始时间 < 结束时间。"""
87+
start_dt = _parse_single_time(start_time or "24h", default_hours=24)
88+
end_dt = _parse_single_time(end_time, default_hours=0) if end_time else datetime.now()
89+
if start_dt >= end_dt:
90+
end_dt = datetime.now()
91+
return int(start_dt.timestamp()), int(end_dt.timestamp())
11192

11293

11394
def _build_controlplane_log_query(
@@ -233,7 +214,19 @@ def __init__(self, server: FastMCP, settings: Optional[Dict[str, Any]] = None):
233214
# Register tools
234215
self.server.tool(
235216
name="query_controlplane_logs",
236-
description="查询ACK集群的控制面组件日志。先查询控制面日志配置,验证组件是否启用,然后查询对应的SLS日志。"
217+
description="""Query ACK cluster control plane component logs.
218+
219+
Function Description:
220+
- Queries control plane component logs from ACK clusters.
221+
- Supports multiple time formats (ISO 8601 and relative time).
222+
- Supports additional filter patterns for log filtering.
223+
- Validates component availability before querying.
224+
- Provides detailed parameter validation and error messages.
225+
226+
Usage Suggestions:
227+
- You can use the list_clusters() tool to view available clusters and their IDs.
228+
- By default, it queries the control plane logs for the last 24 hours. The number of returned records is limited to 10 by default.
229+
- Supported components: apiserver, kcm, scheduler, ccm, etcd, kubelet, kube-proxy, kube-scheduler, kube-controller-manager, kube-apiserver."""
237230
)(self.query_controlplane_logs)
238231

239232
logger.info("ACK Control Plane Log Handler initialized")
@@ -272,14 +265,39 @@ def _get_cluster_region(self, cs_client, cluster_id: str) -> str:
272265
async def query_controlplane_logs(
273266
self,
274267
ctx: Context,
275-
cluster_id: str = Field(..., description="集群ID,例如 cxxxxx"),
276-
component_name: str = Field(..., description="控制面组件的名称,如 apiserver, kcm, scheduler, ccm"),
277-
filter_pattern: Optional[str] = Field(None, description="额外过滤条件"),
278-
start_time: Optional[str] = Field("24h",
279-
description="查询开始时间,支持格式为 ISO 8601 (如: 2025-09-16T08:09:44Z)"),
280-
end_time: Optional[str] = Field(None,
281-
description="查询结束时间,支持格式为 ISO 8601 (如: 2025-09-16T08:09:44Z)"),
282-
limit: Optional[int] = Field(10, description="结果限制,默认10,最大100"),
268+
cluster_id: str = Field(..., description="集群ID"),
269+
component_name: str = Field(..., description="""控制面组件的名称,枚举值:
270+
apiserver: API Server组件
271+
kcm: KCM组件
272+
scheduler: Scheduler组件
273+
ccm: CCM组件
274+
"""),
275+
filter_pattern: Optional[str] = Field(None, description="""(Optional) Additional filter pattern.
276+
Example:
277+
- 查询pod相关的日志: "coredns-8bd9456cc-wck2l"
278+
Defaults to '*' (all logs)."""
279+
),
280+
start_time: str = Field(
281+
"24h",
282+
description="""(Optional) Query start time.
283+
Formats:
284+
- ISO 8601: "2024-01-01T10:00:00Z"
285+
- Relative: "30m", "1h", "24h", "7d"
286+
Defaults to 24h."""
287+
),
288+
end_time: Optional[str] = Field(
289+
None,
290+
description="""(Optional) Query end time.
291+
Formats:
292+
- ISO 8601: "2024-01-01T10:00:00Z"
293+
- Relative: "30m", "1h", "24h", "7d"
294+
Defaults to current time."""
295+
),
296+
limit: int = Field(
297+
10,
298+
ge=1, le=100,
299+
description="(Optional) Result limit, defaults to 10. Maximum is 100."
300+
),
283301
) -> QueryControlPlaneLogsOutput:
284302
"""查询ACK集群的控制面组件日志
285303
@@ -430,9 +448,8 @@ async def query_controlplane_logs(
430448
'annotation') else "24h"
431449
end_time_str = end_time if isinstance(end_time, str) and not hasattr(end_time, 'annotation') else None
432450

433-
# SLS API 需要秒级时间戳
434-
start_timestamp_s = _parse_time(start_time_str)
435-
end_timestamp_s = _parse_time(end_time_str) if end_time_str else int(datetime.now().timestamp())
451+
# SLS API 需要秒级时间戳(与审计日志时间解析策略对齐)
452+
start_timestamp_s, end_timestamp_s = _parse_time_params(start_time_str, end_time_str)
436453

437454
# 步骤4: 构建SLS查询语句
438455
logger.info(f"Step 4: Building SLS query for component {component_name}")

0 commit comments

Comments
 (0)