dev_1.0.0_yuyr_2:重新提交 PR,增加 master/agent 以及系统集成测试 (#17)

Reviewed-on: #17
Reviewed-by: sundapeng <sundp@mail.zgclab.edu.cn>
Reviewed-by: xuxt <xuxt@zgclab.edu.cn>
This commit was merged in pull request #17.
This commit is contained in:
2025-10-11 15:04:46 +08:00
parent 8a38d3d0b2
commit 1e5e91b193
105 changed files with 6096 additions and 107 deletions

155
src/master/app/nodes_api.py Normal file
View File

@@ -0,0 +1,155 @@
from __future__ import annotations
import logging
from http import HTTPStatus
from typing import Any, Mapping
from flask import Blueprint, jsonify, request
from .models import (
ValidationError,
validate_config_payload,
validate_registration_payload,
validate_status_payload,
)
from .scheduler import StatusScheduler
from .storage import Storage
from .util import to_iso, utcnow
def create_nodes_blueprint(storage: Storage, scheduler: StatusScheduler) -> Blueprint:
bp = Blueprint("nodes", __name__)
logger = logging.getLogger("argus.master.api")
def _json_error(message: str, status: HTTPStatus, code: str) -> Any:
response = jsonify({"error": message, "code": code})
response.status_code = status
return response
@bp.errorhandler(ValidationError)
def _handle_validation_error(err: ValidationError):
return _json_error(str(err), HTTPStatus.BAD_REQUEST, "invalid_request")
@bp.get("/nodes")
def list_nodes():
nodes = storage.list_nodes()
return jsonify(nodes)
@bp.get("/nodes/<node_id>")
def get_node(node_id: str):
node = storage.get_node(node_id)
if node is None:
return _json_error("Node not found", HTTPStatus.NOT_FOUND, "not_found")
return jsonify(node)
@bp.post("/nodes")
def register_node():
payload = _get_json()
data = validate_registration_payload(payload)
now = utcnow()
now_iso = to_iso(now)
node_id = data["id"]
name = data["name"]
node_type = data["type"]
version = data["version"]
meta = data["meta_data"]
if node_id:
# 携带 id 说明是重注册,需要校验名称一致性
existing_row = storage.get_node_raw(node_id)
if existing_row is None:
return _json_error("Node not found", HTTPStatus.NOT_FOUND, "not_found")
if existing_row["name"] != name:
return _json_error(
"Node id and name mismatch during re-registration",
HTTPStatus.INTERNAL_SERVER_ERROR,
"id_name_mismatch",
)
updated = storage.update_node_meta(
node_id,
node_type=node_type,
version=version,
meta_data=meta,
last_updated_iso=now_iso,
)
scheduler.trigger_nodes_json_refresh()
return jsonify(updated), HTTPStatus.OK
# No id provided → search by name
existing_by_name = storage.get_node_by_name(name)
if existing_by_name:
# 同名节点已存在,视为无 id 重注册
updated = storage.update_node_meta(
existing_by_name["id"],
node_type=node_type,
version=version,
meta_data=meta,
last_updated_iso=now_iso,
)
scheduler.trigger_nodes_json_refresh()
return jsonify(updated), HTTPStatus.OK
new_id = storage.allocate_node_id()
created = storage.create_node(
new_id,
name,
node_type,
version,
meta,
status="initialized",
register_time_iso=now_iso,
last_updated_iso=now_iso,
)
scheduler.trigger_nodes_json_refresh()
return jsonify(created), HTTPStatus.CREATED
@bp.put("/nodes/<node_id>/config")
def update_node_config(node_id: str):
payload = _get_json()
updates = validate_config_payload(payload)
try:
updated = storage.update_config_and_labels(
node_id,
config=updates.get("config"),
labels=updates.get("label"),
)
except KeyError:
return _json_error("Node not found", HTTPStatus.NOT_FOUND, "not_found")
if "label" in updates:
scheduler.trigger_nodes_json_refresh()
return jsonify(updated)
@bp.get("/nodes/statistics")
def node_statistics():
stats = storage.get_statistics()
return jsonify(stats)
@bp.put("/nodes/<node_id>/status")
def update_status(node_id: str):
payload = _get_json()
data = validate_status_payload(payload)
try:
# master 负责写入 last_report,状态由调度器计算
updated = storage.update_last_report(
node_id,
server_timestamp_iso=to_iso(utcnow()),
agent_timestamp_iso=data["timestamp"],
health=data["health"],
)
except KeyError:
return _json_error("Node not found", HTTPStatus.NOT_FOUND, "not_found")
scheduler.trigger_nodes_json_refresh()
return jsonify(updated)
return bp
def _get_json() -> Mapping[str, Any]:
data = request.get_json(silent=True)
if data is None:
raise ValidationError("Request body must be valid JSON")
if not isinstance(data, Mapping):
raise ValidationError("Request body must be a JSON object")
return data