argus/src/master/app/nodes_api.py

156 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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