mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-30 19:59:36 +02:00
Update to v8.16.0
This commit is contained in:
@@ -261,6 +261,34 @@ class ServerNode:
|
||||
return data, ""
|
||||
return None, "data in wrong format: %s" % str(data)
|
||||
|
||||
def get_service_status(self, services) -> Tuple[List[dict], str]:
|
||||
from mod.project.node.nodeutil.monitor_service import normalize_service_targets
|
||||
service_list = [item for item in normalize_service_targets(services) if int(item.get("enabled", 1)) == 1]
|
||||
if not service_list:
|
||||
return [], ""
|
||||
data, err = self._request("/mod/node/monitor/service_status", "", pdata={
|
||||
"services": json.dumps(service_list),
|
||||
})
|
||||
if err:
|
||||
return [], err
|
||||
if isinstance(data, dict) and isinstance(data.get("services"), list):
|
||||
return data["services"], ""
|
||||
if isinstance(data, list):
|
||||
return data, ""
|
||||
if isinstance(data, str):
|
||||
return [], data
|
||||
return [], "data in wrong format: %s" % str(data)
|
||||
|
||||
def get_service_options(self) -> Tuple[Dict[str, list], str]:
|
||||
data, err = self._request("/mod/node/monitor/get_service_options", "", pdata={})
|
||||
if err:
|
||||
return {"services": [], "groups": []}, err
|
||||
if isinstance(data, dict) and isinstance(data.get("services"), list):
|
||||
if not isinstance(data.get("groups"), list):
|
||||
data["groups"] = []
|
||||
return data, ""
|
||||
return {"services": [], "groups": []}, "data in wrong format: %s" % str(data)
|
||||
|
||||
def get_tmp_token(self) -> Tuple[str, str]:
|
||||
data, err = self._request("/config", "get_tmp_token")
|
||||
if err:
|
||||
@@ -1047,6 +1075,107 @@ class ServerNode:
|
||||
return ""
|
||||
|
||||
|
||||
def _monitor_setting_enabled(monitor_db, node_id: int) -> bool:
|
||||
try:
|
||||
setting = monitor_db.get_setting(node_id, create=True)
|
||||
return int(setting.get("enabled", 1)) == 1
|
||||
except Exception:
|
||||
if public.is_debug():
|
||||
public.print_error()
|
||||
return False
|
||||
|
||||
|
||||
def _monitor_should_save_snapshot(monitor_db, node_id: int) -> bool:
|
||||
try:
|
||||
setting = monitor_db.get_setting(node_id, create=True)
|
||||
if int(setting.get("enabled", 1)) != 1:
|
||||
return False
|
||||
interval = max(int(setting.get("collect_interval", 60)), 60)
|
||||
latest = monitor_db.get_latest(node_id)
|
||||
if latest and int(time.time()) - int(latest.get("ts", 0)) < interval:
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
if public.is_debug():
|
||||
public.print_error()
|
||||
return False
|
||||
|
||||
|
||||
def _attach_monitor_service_data(monitor_db, node_id: int, srv, data: dict) -> dict:
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
services = []
|
||||
try:
|
||||
services = monitor_db.get_enabled_service_targets(node_id)
|
||||
if not services:
|
||||
data["services"] = []
|
||||
return data
|
||||
|
||||
from mod.project.node.nodeutil.monitor_service import collect_local_service_status, service_unknown_statuses
|
||||
if getattr(srv, "is_local", False):
|
||||
service_data = collect_local_service_status(services)
|
||||
elif hasattr(srv, "get_service_status"):
|
||||
service_data, err = srv.get_service_status(services)
|
||||
if err and not service_data:
|
||||
service_data, err = _get_monitor_service_status_by_saved_ssh(node_id, services)
|
||||
if err and not service_data:
|
||||
service_data = service_unknown_statuses(services, err)
|
||||
else:
|
||||
service_data = service_unknown_statuses(services, "Service status check is not supported")
|
||||
data["services"] = service_data
|
||||
except Exception as e:
|
||||
from mod.project.node.nodeutil.monitor_service import service_unknown_statuses
|
||||
data["services"] = service_unknown_statuses(services, str(e))
|
||||
if public.is_debug():
|
||||
public.print_error()
|
||||
return data
|
||||
|
||||
|
||||
def _get_monitor_service_status_by_saved_ssh(node_id: int, services: List[Dict[str, Any]]) -> Tuple[List[dict], str]:
|
||||
try:
|
||||
node_data = public.S('node', public.get_panel_path() + "/data/db/node.db").where("id=?", (int(node_id),)).find()
|
||||
if not isinstance(node_data, dict) or not node_data:
|
||||
return [], "Node does not exist"
|
||||
ssh_conf = node_data.get("ssh_conf", {})
|
||||
if isinstance(ssh_conf, str):
|
||||
ssh_conf = json.loads(ssh_conf or "{}")
|
||||
if not isinstance(ssh_conf, dict) or not ssh_conf:
|
||||
return [], "Service status check is not supported by the remote node"
|
||||
from mod.project.node.nodeutil.ssh_wrap import SSHApi
|
||||
return SSHApi(**ssh_conf).get_service_status(services)
|
||||
except Exception as e:
|
||||
return [], str(e)
|
||||
|
||||
|
||||
def _save_monitor_snapshot(node_id: int, srv, data: dict):
|
||||
try:
|
||||
from mod.project.node.dbutil import NodeMonitorDB
|
||||
monitor_db = NodeMonitorDB()
|
||||
if not _monitor_should_save_snapshot(monitor_db, node_id):
|
||||
return
|
||||
data = _attach_monitor_service_data(monitor_db, node_id, srv, data)
|
||||
err = monitor_db.save_node_snapshot(node_id, data, status=1)
|
||||
if err and public.is_debug():
|
||||
public.print_log("Node monitor snapshot save failed: {}".format(err))
|
||||
except Exception:
|
||||
if public.is_debug():
|
||||
public.print_error()
|
||||
|
||||
|
||||
def _save_monitor_status(node_id: int, status: int, error_msg: str):
|
||||
try:
|
||||
from mod.project.node.dbutil import NodeMonitorDB
|
||||
monitor_db = NodeMonitorDB()
|
||||
if not _monitor_setting_enabled(monitor_db, node_id):
|
||||
return
|
||||
err = monitor_db.save_latest_status(node_id, status, error_msg)
|
||||
if err and public.is_debug():
|
||||
public.print_log("Node monitor latest status save failed: {}".format(err))
|
||||
except Exception:
|
||||
if public.is_debug():
|
||||
public.print_error()
|
||||
|
||||
|
||||
def monitor_all_node_status():
|
||||
all_nodes = public.S('node', public.get_panel_path() + "/data/db/node.db").select()
|
||||
if not isinstance(all_nodes, list) or not all_nodes:
|
||||
@@ -1063,6 +1192,14 @@ def monitor_all_node_status():
|
||||
# 等待所有线程完成(可选)
|
||||
for t in threads:
|
||||
t.join()
|
||||
try:
|
||||
from mod.project.node.dbutil import NodeMonitorDB
|
||||
err = NodeMonitorDB().cleanup_expired_snapshots()
|
||||
if err and public.is_debug():
|
||||
public.print_log("Node monitor snapshot cleanup failed: {}".format(err))
|
||||
except Exception:
|
||||
if public.is_debug():
|
||||
public.print_error()
|
||||
|
||||
|
||||
def monitor_node_once(node_data: dict):
|
||||
@@ -1071,6 +1208,11 @@ def monitor_node_once(node_data: dict):
|
||||
from mod.base.ssh_executor import test_ssh_config
|
||||
try:
|
||||
if node_data["app_key"] == "local" and node_data["api_key"] == "local":
|
||||
data = ServerMonitorRepo.get_local_server_status()
|
||||
if data:
|
||||
_save_monitor_snapshot(int(node_data.get("id", 0)), LocalNode(), data)
|
||||
else:
|
||||
_save_monitor_status(int(node_data.get("id", 0)), 2, "Failed to obtain local node data")
|
||||
return
|
||||
node_data["error"] = json.loads(node_data["error"])
|
||||
node_data["ssh_conf"] = json.loads(node_data["ssh_conf"])
|
||||
@@ -1098,6 +1240,7 @@ def monitor_node_once(node_data: dict):
|
||||
ServerMonitorRepo().remove_cache(node.id)
|
||||
|
||||
ServerNodeDB().update_node(node)
|
||||
_save_monitor_status(node.id, 0 if node.error_num >= 2 else 2, err)
|
||||
|
||||
else:
|
||||
if node.error_num > 0:
|
||||
@@ -1109,6 +1252,7 @@ def monitor_node_once(node_data: dict):
|
||||
if data:
|
||||
repo = ServerMonitorRepo()
|
||||
repo.save_server_status(node.id, data)
|
||||
_save_monitor_snapshot(node.id, srv, data)
|
||||
if repo.is_reboot_wait(node.server_ip):
|
||||
repo.set_wait_reboot(node.server_ip, False)
|
||||
if not node_data["ssh_test"] and not node_data["ssh_conf"] and not isinstance(srv, SSHApi):
|
||||
@@ -1144,6 +1288,12 @@ def monitor_node_once(node_data: dict):
|
||||
|
||||
def monitor_node_once_with_timeout(node_data: dict, timeout: int = 5):
|
||||
if node_data["app_key"] == "local" and node_data["api_key"] == "local":
|
||||
from mod.project.node.dbutil import ServerMonitorRepo
|
||||
data = ServerMonitorRepo.get_local_server_status()
|
||||
if data:
|
||||
_save_monitor_snapshot(int(node_data.get("id", 0)), LocalNode(), data)
|
||||
else:
|
||||
_save_monitor_status(int(node_data.get("id", 0)), 2, "Failed to obtain local node data")
|
||||
return
|
||||
from mod.project.node.dbutil import Node, ServerNodeDB, ServerMonitorRepo
|
||||
from mod.project.node.nodeutil.ssh_wrap import SSHApi
|
||||
@@ -1176,6 +1326,7 @@ def monitor_node_once_with_timeout(node_data: dict, timeout: int = 5):
|
||||
ServerMonitorRepo().remove_cache(node.id)
|
||||
|
||||
ServerNodeDB().update_node(node)
|
||||
_save_monitor_status(node.id, 0 if node.error_num >= 2 else 2, err)
|
||||
|
||||
elif node.error_num > 0:
|
||||
node.error_num = 0
|
||||
@@ -1186,6 +1337,7 @@ def monitor_node_once_with_timeout(node_data: dict, timeout: int = 5):
|
||||
if data:
|
||||
repo = ServerMonitorRepo()
|
||||
repo.save_server_status(node.id, data)
|
||||
_save_monitor_snapshot(node.id, srv, data)
|
||||
if repo.is_reboot_wait(node.server_ip):
|
||||
repo.set_wait_reboot(node.server_ip, False)
|
||||
except Exception as e:
|
||||
@@ -1958,4 +2110,4 @@ API Response Status: 200
|
||||
key, err = self.lpc.get_file_body("/root/.ssh/id_ed25519_1panel")
|
||||
if err:
|
||||
return None, "Failed to read SSH key, error message is: %s" % err
|
||||
return key, ""
|
||||
return key, ""
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
# coding: utf-8
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
import public
|
||||
|
||||
|
||||
SERVICE_NAME_RE = re.compile(r"^[A-Za-z0-9_.@:+-]+$")
|
||||
|
||||
DEFAULT_SERVICES = [
|
||||
{"target_key": "nginx", "target_name": "nginx"},
|
||||
{"target_key": "httpd", "target_name": "apache"},
|
||||
{"target_key": "mysqld", "target_name": "mysql"},
|
||||
{"target_key": "mariadbd", "target_name": "mariadb"},
|
||||
{"target_key": "redis", "target_name": "redis"},
|
||||
{"target_key": "pure-ftpd", "target_name": "pure-ftpd"},
|
||||
{"target_key": "memcached", "target_name": "memcached"},
|
||||
{"target_key": "mongodb", "target_name": "mongodb"},
|
||||
{"target_key": "pgsql", "target_name": "pgsql"},
|
||||
]
|
||||
|
||||
LOCAL_SERVICE_DEFINITIONS = [
|
||||
{
|
||||
"family": "nginx",
|
||||
"target_key": "nginx",
|
||||
"target_name": "Nginx",
|
||||
"paths": ["/www/server/nginx/sbin/nginx", "/www/server/nginx/nginx/sbin/nginx", "/etc/init.d/nginx"],
|
||||
},
|
||||
{
|
||||
"family": "mysql",
|
||||
"target_key": "mysqld",
|
||||
"target_name": "MySQL",
|
||||
"paths": ["/www/server/mysql/bin/mysqld", "/etc/init.d/mysqld"],
|
||||
},
|
||||
{
|
||||
"family": "apache",
|
||||
"target_key": "httpd",
|
||||
"target_name": "Apache",
|
||||
"paths": ["/www/server/apache/bin/httpd", "/etc/init.d/httpd"],
|
||||
},
|
||||
{
|
||||
"family": "redis",
|
||||
"target_key": "redis",
|
||||
"target_name": "Redis",
|
||||
"paths": ["/www/server/redis/src/redis-server", "/etc/init.d/redis"],
|
||||
},
|
||||
{
|
||||
"family": "pure-ftpd",
|
||||
"target_key": "pure-ftpd",
|
||||
"target_name": "Pure-Ftpd",
|
||||
"paths": ["/www/server/pure-ftpd/sbin/pure-ftpd", "/etc/init.d/pure-ftpd"],
|
||||
},
|
||||
{
|
||||
"family": "memcached",
|
||||
"target_key": "memcached",
|
||||
"target_name": "Memcached",
|
||||
"paths": ["/usr/local/memcached/bin/memcached", "/etc/init.d/memcached"],
|
||||
},
|
||||
{
|
||||
"family": "mongodb",
|
||||
"target_key": "mongodb",
|
||||
"target_name": "MongoDB",
|
||||
"paths": ["/www/server/mongodb/bin/mongod", "/etc/init.d/mongodb"],
|
||||
},
|
||||
{
|
||||
"family": "pgsql",
|
||||
"target_key": "pgsql",
|
||||
"target_name": "PostgreSQL",
|
||||
"paths": ["/www/server/pgsql/bin/postgres", "/etc/init.d/pgsql"],
|
||||
},
|
||||
]
|
||||
|
||||
PROCESS_CHECK_SERVICES = {
|
||||
"mysqld",
|
||||
"mariadbd",
|
||||
"redis",
|
||||
"pure-ftpd",
|
||||
"nginx",
|
||||
"httpd",
|
||||
"apache",
|
||||
"memcached",
|
||||
"mongodb",
|
||||
"pgsql",
|
||||
}
|
||||
|
||||
|
||||
def valid_service_name(name: str) -> bool:
|
||||
return bool(name and SERVICE_NAME_RE.match(name))
|
||||
|
||||
|
||||
def normalize_service_targets(services: Any) -> List[Dict[str, Any]]:
|
||||
if not isinstance(services, list):
|
||||
return []
|
||||
result = []
|
||||
seen = set()
|
||||
for idx, item in enumerate(services):
|
||||
if isinstance(item, str):
|
||||
key = item.strip()
|
||||
name = key
|
||||
enabled = 1
|
||||
sort = idx
|
||||
extra = {}
|
||||
elif isinstance(item, dict):
|
||||
key = str(item.get("target_key") or item.get("name") or "").strip()
|
||||
name = str(item.get("target_name") or item.get("name") or key).strip()
|
||||
enabled = int(item.get("enabled", 1))
|
||||
sort = int(item.get("sort", idx))
|
||||
extra = item.get("extra", {})
|
||||
else:
|
||||
continue
|
||||
if not valid_service_name(key) or key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
service_family = str(item.get("service_family") or item.get("family") or "").strip() if isinstance(item, dict) else ""
|
||||
if not service_family and key.startswith("php-fpm-"):
|
||||
service_family = "php"
|
||||
result.append({
|
||||
"target_key": key,
|
||||
"target_name": name or key,
|
||||
"enabled": 1 if enabled else 0,
|
||||
"sort": sort,
|
||||
"extra": extra if isinstance(extra, dict) else {},
|
||||
"service_family": service_family,
|
||||
"version": str(item.get("version") or "").strip() if isinstance(item, dict) else "",
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def keep_single_version_services(services: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
result = []
|
||||
seen_version_family = set()
|
||||
for item in normalize_service_targets(services):
|
||||
family = item.get("service_family") or ("php" if item["target_key"].startswith("php-fpm-") else "")
|
||||
if family == "php":
|
||||
if family in seen_version_family:
|
||||
continue
|
||||
seen_version_family.add(family)
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def service_unknown_statuses(services: List[Dict[str, Any]], error_msg: str) -> List[Dict[str, Any]]:
|
||||
now = int(time.time())
|
||||
result = []
|
||||
for item in normalize_service_targets(services):
|
||||
result.append({
|
||||
"name": item["target_key"],
|
||||
"target_key": item["target_key"],
|
||||
"target_name": item["target_name"],
|
||||
"status": 2,
|
||||
"running": False,
|
||||
"state": "unknown",
|
||||
"error_msg": error_msg,
|
||||
"ts": now,
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def collect_local_service_status(services: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
now = int(time.time())
|
||||
result = []
|
||||
for item in normalize_service_targets(services):
|
||||
if not int(item.get("enabled", 1)):
|
||||
continue
|
||||
running, err = _check_local_service(item["target_key"])
|
||||
status = 1 if running else 0
|
||||
if err and not running:
|
||||
status = 2
|
||||
result.append({
|
||||
"name": item["target_key"],
|
||||
"target_key": item["target_key"],
|
||||
"target_name": item["target_name"],
|
||||
"status": status,
|
||||
"running": bool(running),
|
||||
"state": "active" if running else ("unknown" if err else "inactive"),
|
||||
"error_msg": err,
|
||||
"ts": now,
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def discover_local_services() -> Dict[str, Any]:
|
||||
services = []
|
||||
for item in LOCAL_SERVICE_DEFINITIONS:
|
||||
if not any(os.path.exists(path) for path in item["paths"]):
|
||||
continue
|
||||
status = collect_local_service_status([{
|
||||
"target_key": item["target_key"],
|
||||
"target_name": item["target_name"],
|
||||
"service_family": item["family"],
|
||||
}])
|
||||
status_item = status[0] if status else {}
|
||||
services.append(_service_option(
|
||||
target_key=item["target_key"],
|
||||
target_name=item["target_name"],
|
||||
family=item["family"],
|
||||
running=bool(status_item.get("running", False)),
|
||||
status=int(status_item.get("status", 0)),
|
||||
state=str(status_item.get("state") or "inactive"),
|
||||
))
|
||||
|
||||
for version in _installed_php_versions():
|
||||
key = "php-fpm-{}".format(version)
|
||||
status = collect_local_service_status([{
|
||||
"target_key": key,
|
||||
"target_name": "PHP {}".format(_format_php_version(version)),
|
||||
"service_family": "php",
|
||||
"version": version,
|
||||
}])
|
||||
status_item = status[0] if status else {}
|
||||
services.append(_service_option(
|
||||
target_key=key,
|
||||
target_name="PHP {}".format(_format_php_version(version)),
|
||||
family="php",
|
||||
version=version,
|
||||
version_name=_format_php_version(version),
|
||||
running=bool(status_item.get("running", False)),
|
||||
status=int(status_item.get("status", 0)),
|
||||
state=str(status_item.get("state") or "inactive"),
|
||||
is_versioned=1,
|
||||
))
|
||||
|
||||
return {
|
||||
"services": services,
|
||||
"groups": build_service_groups(services),
|
||||
}
|
||||
|
||||
|
||||
def get_service_file_probe_paths() -> List[str]:
|
||||
paths = []
|
||||
for item in LOCAL_SERVICE_DEFINITIONS:
|
||||
paths.extend(item["paths"])
|
||||
for version in _candidate_php_versions():
|
||||
paths.extend(_php_version_paths(version))
|
||||
return _dedupe(paths)
|
||||
|
||||
|
||||
def build_service_options_from_file_exists(existing_paths: Any) -> Dict[str, Any]:
|
||||
if isinstance(existing_paths, (list, tuple, set)):
|
||||
exists_map = {str(path): True for path in existing_paths}
|
||||
elif isinstance(existing_paths, dict):
|
||||
exists_map = {str(path): bool(value) for path, value in existing_paths.items()}
|
||||
else:
|
||||
exists_map = {}
|
||||
|
||||
services = []
|
||||
for item in LOCAL_SERVICE_DEFINITIONS:
|
||||
if not any(exists_map.get(path) for path in item["paths"]):
|
||||
continue
|
||||
services.append(_service_option(
|
||||
target_key=item["target_key"],
|
||||
target_name=item["target_name"],
|
||||
family=item["family"],
|
||||
status=2,
|
||||
state="unknown",
|
||||
))
|
||||
|
||||
for version in _candidate_php_versions():
|
||||
if not any(exists_map.get(path) for path in _php_version_paths(version)):
|
||||
continue
|
||||
services.append(_service_option(
|
||||
target_key="php-fpm-{}".format(version),
|
||||
target_name="PHP {}".format(_format_php_version(version)),
|
||||
family="php",
|
||||
version=version,
|
||||
version_name=_format_php_version(version),
|
||||
status=2,
|
||||
state="unknown",
|
||||
is_versioned=1,
|
||||
))
|
||||
|
||||
return {
|
||||
"services": services,
|
||||
"groups": build_service_groups(services),
|
||||
}
|
||||
|
||||
|
||||
def build_service_groups(services: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
groups = []
|
||||
group_map = {}
|
||||
for item in services:
|
||||
family = item.get("service_family") or item.get("family") or item.get("target_key")
|
||||
if not family:
|
||||
continue
|
||||
if family not in group_map:
|
||||
group_map[family] = {
|
||||
"family": family,
|
||||
"target_name": "PHP" if family == "php" else item.get("target_name", family),
|
||||
"type": "version" if family == "php" else "single",
|
||||
"select_mode": "single" if family == "php" else "toggle",
|
||||
"multiple": 0 if family == "php" else 1,
|
||||
"services": [],
|
||||
}
|
||||
groups.append(group_map[family])
|
||||
group_map[family]["services"].append(item)
|
||||
|
||||
for group in groups:
|
||||
if group["family"] == "php":
|
||||
group["services"].sort(key=lambda x: x.get("version", ""))
|
||||
else:
|
||||
group["services"].sort(key=lambda x: x.get("sort", 0))
|
||||
return groups
|
||||
|
||||
|
||||
def _installed_php_versions() -> List[str]:
|
||||
versions = []
|
||||
for version in _candidate_php_versions():
|
||||
for path in _php_version_paths(version):
|
||||
if os.path.exists(path):
|
||||
versions.append(str(version))
|
||||
break
|
||||
return versions
|
||||
|
||||
|
||||
def _candidate_php_versions() -> List[str]:
|
||||
try:
|
||||
versions = public.get_php_versions()
|
||||
except Exception:
|
||||
versions = ['52', '53', '54', '55', '56', '70', '71', '72', '73', '74', '80', '81', '82', '83', '84', '85']
|
||||
return [str(version) for version in versions if re.match(r"^\d{2}$", str(version))]
|
||||
|
||||
|
||||
def _php_version_paths(version: str) -> List[str]:
|
||||
return [
|
||||
"/www/server/php/{}/sbin/php-fpm".format(version),
|
||||
"/www/server/php/{}/bin/php".format(version),
|
||||
"/etc/init.d/php-fpm-{}".format(version),
|
||||
]
|
||||
|
||||
|
||||
def _dedupe(items: List[str]) -> List[str]:
|
||||
result = []
|
||||
seen = set()
|
||||
for item in items:
|
||||
if item in seen:
|
||||
continue
|
||||
seen.add(item)
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def _format_php_version(version: str) -> str:
|
||||
version = str(version)
|
||||
if len(version) == 2 and version.isdigit():
|
||||
return "{}.{}".format(version[0], version[1])
|
||||
return version
|
||||
|
||||
|
||||
def _service_option(
|
||||
target_key: str,
|
||||
target_name: str,
|
||||
family: str,
|
||||
version: str = "",
|
||||
version_name: str = "",
|
||||
running: bool = False,
|
||||
status: int = 0,
|
||||
state: str = "inactive",
|
||||
is_versioned: int = 0,
|
||||
) -> Dict[str, Any]:
|
||||
return {
|
||||
"target_type": "service",
|
||||
"target_key": target_key,
|
||||
"target_name": target_name,
|
||||
"service_family": family,
|
||||
"version": version,
|
||||
"version_name": version_name,
|
||||
"enabled": 0,
|
||||
"installed": 1,
|
||||
"is_versioned": int(is_versioned),
|
||||
"status": int(status),
|
||||
"running": bool(running),
|
||||
"state": state,
|
||||
"sort": 0,
|
||||
}
|
||||
|
||||
|
||||
def _check_local_service(name: str) -> Tuple[bool, str]:
|
||||
if not valid_service_name(name):
|
||||
return False, "Invalid service name"
|
||||
if name in PROCESS_CHECK_SERVICES or name.find("php-fpm") != -1:
|
||||
try:
|
||||
from system import system
|
||||
return bool(system().check_service_status(name)), ""
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
cmd = (
|
||||
"if command -v systemctl >/dev/null 2>&1; then "
|
||||
"systemctl is-active --quiet {name}; "
|
||||
"else service {name} status >/dev/null 2>&1; fi; echo $?"
|
||||
).format(name=name)
|
||||
try:
|
||||
stdout, stderr = public.ExecShell(cmd)
|
||||
lines = str(stdout or "").strip().splitlines()
|
||||
return bool(lines and lines[-1].strip() == "0"), str(stderr or "")
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
@@ -1,9 +1,11 @@
|
||||
import json
|
||||
import os.path
|
||||
import time
|
||||
import traceback
|
||||
from typing import Optional, Tuple, Callable, Union, Dict
|
||||
from typing import Optional, Tuple, Callable, Union, Dict, List
|
||||
from mod.base.ssh_executor import SSHExecutor, CommandResult
|
||||
from mod.project.node.dbutil import ServerNodeDB, Node
|
||||
from mod.project.node.nodeutil.monitor_service import build_service_groups, normalize_service_targets, service_unknown_statuses
|
||||
|
||||
import public
|
||||
|
||||
@@ -13,6 +15,11 @@ def is_much_difference(a:float, b:float)->bool:
|
||||
ratio = a / b
|
||||
return ratio >= 10 or ratio <= 0.1
|
||||
|
||||
|
||||
def _shell_single_quote(value: str) -> str:
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
|
||||
class SSHApi:
|
||||
is_local = False
|
||||
_local_scripts_dir = os.path.join(os.path.dirname(__file__), "ssh_warp_scripts")
|
||||
@@ -67,6 +74,153 @@ class SSHApi:
|
||||
except Exception as e:
|
||||
return None, str(e)
|
||||
|
||||
def get_service_status(self, services) -> Tuple[List[dict], str]:
|
||||
service_list = [item for item in normalize_service_targets(services) if int(item.get("enabled", 1)) == 1]
|
||||
if not service_list:
|
||||
return [], ""
|
||||
service_args = " ".join(_shell_single_quote(item["target_key"]) for item in service_list)
|
||||
cmd = """
|
||||
for svc in {service_args}; do
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl is-active --quiet "$svc"
|
||||
rc=$?
|
||||
state=$(systemctl is-active "$svc" 2>/dev/null || true)
|
||||
else
|
||||
service "$svc" status >/dev/null 2>&1
|
||||
rc=$?
|
||||
if [ "$rc" = "0" ]; then state="active"; else state="inactive"; fi
|
||||
fi
|
||||
if [ "$rc" = "0" ]; then running=1; else running=0; fi
|
||||
printf '%s\\t%s\\t%s\\n' "$svc" "$running" "$state"
|
||||
done
|
||||
""".format(service_args=service_args)
|
||||
executor = None
|
||||
try:
|
||||
executor = self._get_ssh_executor()
|
||||
executor.open()
|
||||
exit_code, stdout, stderr = executor.run(cmd)
|
||||
if exit_code != 0 and not stdout:
|
||||
return service_unknown_statuses(service_list, stderr or "Service status check failed"), stderr
|
||||
target_map = {item["target_key"]: item for item in service_list}
|
||||
now = int(time.time())
|
||||
result = []
|
||||
for line in str(stdout or "").splitlines():
|
||||
parts = line.split("\t")
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
key = parts[0].strip()
|
||||
item = target_map.get(key)
|
||||
if not item:
|
||||
continue
|
||||
running = parts[1].strip() == "1"
|
||||
result.append({
|
||||
"name": key,
|
||||
"target_key": key,
|
||||
"target_name": item["target_name"],
|
||||
"status": 1 if running else 0,
|
||||
"running": running,
|
||||
"state": parts[2].strip() or ("active" if running else "inactive"),
|
||||
"error_msg": "",
|
||||
"ts": now,
|
||||
})
|
||||
if not result:
|
||||
return service_unknown_statuses(service_list, stderr or "Service status data is empty"), stderr
|
||||
return result, ""
|
||||
except RuntimeError:
|
||||
return service_unknown_statuses(service_list, "SSH connection failed"), "SSH connection failed"
|
||||
except Exception as e:
|
||||
return service_unknown_statuses(service_list, str(e)), str(e)
|
||||
finally:
|
||||
if executor:
|
||||
executor.close()
|
||||
|
||||
def get_service_options(self) -> Tuple[Dict[str, list], str]:
|
||||
cmd = r"""
|
||||
check_status() {
|
||||
svc="$1"
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl is-active --quiet "$svc"
|
||||
rc=$?
|
||||
state=$(systemctl is-active "$svc" 2>/dev/null || true)
|
||||
else
|
||||
service "$svc" status >/dev/null 2>&1
|
||||
rc=$?
|
||||
if [ "$rc" = "0" ]; then state="active"; else state="inactive"; fi
|
||||
fi
|
||||
if [ "$rc" = "0" ]; then running=1; else running=0; fi
|
||||
printf '%s\t%s' "$running" "$state"
|
||||
}
|
||||
emit_service() {
|
||||
family="$1"; key="$2"; label="$3"; paths="$4"
|
||||
exists=0
|
||||
old_ifs="$IFS"; IFS=":"
|
||||
for p in $paths; do
|
||||
if [ -e "$p" ]; then exists=1; break; fi
|
||||
done
|
||||
IFS="$old_ifs"
|
||||
if [ "$exists" = "1" ]; then
|
||||
status=$(check_status "$key")
|
||||
printf 'SERVICE\t%s\t%s\t%s\t\t\t%s\n' "$family" "$key" "$label" "$status"
|
||||
fi
|
||||
}
|
||||
emit_service nginx nginx Nginx "/www/server/nginx/sbin/nginx:/www/server/nginx/nginx/sbin/nginx:/etc/init.d/nginx"
|
||||
emit_service mysql mysqld MySQL "/www/server/mysql/bin/mysqld:/etc/init.d/mysqld"
|
||||
emit_service apache httpd Apache "/www/server/apache/bin/httpd:/etc/init.d/httpd"
|
||||
emit_service redis redis Redis "/www/server/redis/src/redis-server:/etc/init.d/redis"
|
||||
emit_service pure-ftpd pure-ftpd Pure-Ftpd "/www/server/pure-ftpd/sbin/pure-ftpd:/etc/init.d/pure-ftpd"
|
||||
emit_service memcached memcached Memcached "/usr/local/memcached/bin/memcached:/etc/init.d/memcached"
|
||||
emit_service mongodb mongodb MongoDB "/www/server/mongodb/bin/mongod:/etc/init.d/mongodb"
|
||||
emit_service pgsql pgsql PostgreSQL "/www/server/pgsql/bin/postgres:/etc/init.d/pgsql"
|
||||
if [ -d /www/server/php ]; then
|
||||
for dir in /www/server/php/[0-9][0-9]; do
|
||||
[ -d "$dir" ] || continue
|
||||
ver=$(basename "$dir")
|
||||
if [ -x "$dir/sbin/php-fpm" ] || [ -x "$dir/bin/php" ] || [ -e "/etc/init.d/php-fpm-$ver" ]; then
|
||||
status=$(check_status "php-fpm-$ver")
|
||||
major=$(printf '%s' "$ver" | cut -c1)
|
||||
minor=$(printf '%s' "$ver" | cut -c2)
|
||||
printf 'SERVICE\tphp\tphp-fpm-%s\tPHP %s.%s\t%s\t%s.%s\t%s\n' "$ver" "$major" "$minor" "$ver" "$major" "$minor" "$status"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
"""
|
||||
executor = None
|
||||
try:
|
||||
executor = self._get_ssh_executor()
|
||||
executor.open()
|
||||
exit_code, stdout, stderr = executor.run(cmd)
|
||||
if exit_code != 0 and not stdout:
|
||||
return {"services": [], "groups": []}, stderr or "Service discovery failed"
|
||||
services = []
|
||||
for line in str(stdout or "").splitlines():
|
||||
parts = line.split("\t")
|
||||
if len(parts) < 8 or parts[0] != "SERVICE":
|
||||
continue
|
||||
running = parts[6].strip() == "1"
|
||||
services.append({
|
||||
"target_type": "service",
|
||||
"service_family": parts[1].strip(),
|
||||
"target_key": parts[2].strip(),
|
||||
"target_name": parts[3].strip(),
|
||||
"version": parts[4].strip(),
|
||||
"version_name": parts[5].strip(),
|
||||
"enabled": 0,
|
||||
"installed": 1,
|
||||
"is_versioned": 1 if parts[1].strip() == "php" else 0,
|
||||
"status": 1 if running else 0,
|
||||
"running": running,
|
||||
"state": parts[7].strip() or ("active" if running else "inactive"),
|
||||
"sort": 0,
|
||||
})
|
||||
return {"services": services, "groups": build_service_groups(services)}, ""
|
||||
except RuntimeError:
|
||||
return {"services": [], "groups": []}, "SSH connection failed"
|
||||
except Exception as e:
|
||||
return {"services": [], "groups": []}, str(e)
|
||||
finally:
|
||||
if executor:
|
||||
executor.close()
|
||||
|
||||
@staticmethod
|
||||
def _tans_net_work_form_data(data: dict):
|
||||
data["mem"]["memAvailable"] = round(data["mem"]["memAvailable"] / 1024 / 1024, 2)
|
||||
|
||||
Reference in New Issue
Block a user