914 lines
35 KiB
Python
914 lines
35 KiB
Python
|
|
import json
|
||
|
|
import os.path
|
||
|
|
import threading
|
||
|
|
import time
|
||
|
|
import psutil
|
||
|
|
import traceback
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import List, Dict, Optional, Tuple, MutableMapping, Union
|
||
|
|
|
||
|
|
import simple_websocket
|
||
|
|
from mod.base import json_response, list_args
|
||
|
|
from mod.project.node.nodeutil import ServerNode, LocalNode, LPanelNode, SSHApi
|
||
|
|
from mod.project.node.dbutil import Script, CommandLog, TaskFlowsDB, CommandTask, ServerNodeDB, TransferTask, \
|
||
|
|
ServerMonitorRepo, Flow, FlowTemplates
|
||
|
|
from mod.project.node.task_flow import self_file_running_log, flow_running_log, flow_useful_version, file_task_run_sync, \
|
||
|
|
command_task_run_sync
|
||
|
|
|
||
|
|
import public
|
||
|
|
|
||
|
|
|
||
|
|
class main:
|
||
|
|
next_flow_tip_name = "user_next_flow_tip"
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def create_script(get):
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
err = Script.check(get)
|
||
|
|
if err:
|
||
|
|
return public.return_message(-1, 0,=err)
|
||
|
|
s = Script.from_dict(get)
|
||
|
|
# 查重
|
||
|
|
if e_db.Script.find("name = ?", (s.name,)):
|
||
|
|
return public.return_message(-1, 0,"Script name already exists")
|
||
|
|
err = e_db.Script.create(s)
|
||
|
|
if isinstance(err, str):
|
||
|
|
return public.return_message(-1, 0,err)
|
||
|
|
# return json_response(status=True, msg="Created successfully", data=s.to_dict())
|
||
|
|
return public.return_message(0, 0,s.to_dict())
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def modify_script(get):
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
err = Script.check(get)
|
||
|
|
if err:
|
||
|
|
return public.return_message(-1, 0,err)
|
||
|
|
s = Script.from_dict(get)
|
||
|
|
if not s.id:
|
||
|
|
return public.return_message(-1, 0,"Script ID cannot be empty")
|
||
|
|
if not e_db.Script.find("id = ?", (s.id,)):
|
||
|
|
return public.return_message(-1, 0,"Script does not exist")
|
||
|
|
err = e_db.Script.update(s)
|
||
|
|
if err:
|
||
|
|
return public.return_message(-1, 0,err)
|
||
|
|
# return json_response(status=True, msg="Modified successfully", data=s.to_dict())
|
||
|
|
return public.return_message(0, 0,s.to_dict())
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def delete_script(get):
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
if not get.id:
|
||
|
|
return public.return_message(-1, 0,"Script ID cannot be empty")
|
||
|
|
try:
|
||
|
|
del_id = int(get.id)
|
||
|
|
except:
|
||
|
|
return public.return_message(-1, 0,"Script ID format error")
|
||
|
|
|
||
|
|
e_db.Script.delete(del_id)
|
||
|
|
return public.return_message(0, 0,"Deleted successfully")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def get_script_list(get):
|
||
|
|
page_num = max(int(get.get('p/d', 1)), 1)
|
||
|
|
limit = max(int(get.get('limit/d', 16)), 1)
|
||
|
|
search = get.get('search', "").strip()
|
||
|
|
script_type = get.get('script_type/s', "all")
|
||
|
|
if not script_type in ["all", "python", "shell"]:
|
||
|
|
script_type = "all"
|
||
|
|
|
||
|
|
where_list, params = [], []
|
||
|
|
if search:
|
||
|
|
where_list.append("(name like ? or content like ? or description like ?)")
|
||
|
|
params.append("%{}%".format(search))
|
||
|
|
params.append("%{}%".format(search))
|
||
|
|
params.append("%{}%".format(search))
|
||
|
|
|
||
|
|
if script_type != "all":
|
||
|
|
where_list.append("script_type = ?")
|
||
|
|
params.append(script_type)
|
||
|
|
|
||
|
|
where = " and ".join(where_list)
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
data_list = e_db.Script.query_page(where, (*params,), page_num=page_num, limit=limit)
|
||
|
|
count = e_db.Script.count(where, params)
|
||
|
|
page = public.get_page(count, page_num, limit)
|
||
|
|
page["data"] = [i.to_dict() for i in data_list]
|
||
|
|
return page
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def bath_delete_script(get):
|
||
|
|
script_ids = list_args(get, 'script_ids')
|
||
|
|
try:
|
||
|
|
script_ids = [int(i) for i in script_ids]
|
||
|
|
except:
|
||
|
|
return public.return_message(-1, 0,"Script ID format error")
|
||
|
|
if not script_ids:
|
||
|
|
return public.return_message(-1, 0,"Script ID cannot be empty")
|
||
|
|
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
err = e_db.Script.delete(script_ids)
|
||
|
|
if err:
|
||
|
|
return public.return_message(-1, 0,err)
|
||
|
|
return public.return_message(0, 0,"Deleted successfully")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def create_task(get):
|
||
|
|
node_ids = list_args(get, 'node_ids')
|
||
|
|
if not node_ids:
|
||
|
|
return public.return_message(-1, 0,"Node ID cannot be empty")
|
||
|
|
try:
|
||
|
|
node_ids = [int(i) for i in node_ids]
|
||
|
|
except:
|
||
|
|
return public.return_message(-1, 0,"Node ID format error")
|
||
|
|
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
script_id = get.get('script_id/d', 0)
|
||
|
|
if script_id:
|
||
|
|
s = e_db.Script.find("id = ?", (script_id,))
|
||
|
|
if not s:
|
||
|
|
return public.return_message(-1, 0,"Script does not exist")
|
||
|
|
|
||
|
|
elif get.get("script_content/s", "").strip():
|
||
|
|
if not (get.get("script_type", "").strip() in ("python", "shell")):
|
||
|
|
return public.return_message(-1, 0,"Script type error")
|
||
|
|
s = Script("", get.get("script_type", "").strip(), content=get.get("script_content", "").strip())
|
||
|
|
s.id = 0
|
||
|
|
else:
|
||
|
|
return public.return_message(-1, 0,"Please select a script")
|
||
|
|
|
||
|
|
nodes_db = ServerNodeDB()
|
||
|
|
nodes = []
|
||
|
|
timestamp = int(datetime.now().timestamp())
|
||
|
|
for i in node_ids:
|
||
|
|
n = nodes_db.get_node_by_id(i)
|
||
|
|
if not n:
|
||
|
|
return public.return_message(-1, 0,"The node with node ID [{}] does not exist".format(i))
|
||
|
|
n["ssh_conf"] = json.loads(n["ssh_conf"])
|
||
|
|
if not n["ssh_conf"]:
|
||
|
|
return public.return_message(-1, 0,"The node with node ID [{}] has not configured SSH information and cannot distribute instructions".format(i))
|
||
|
|
n["log_name"] = "{}_{}_{}.log".format(public.md5(s.content)[::2], timestamp, n['remarks'])
|
||
|
|
nodes.append(n)
|
||
|
|
|
||
|
|
e_task = CommandTask(
|
||
|
|
script_id=s.id,
|
||
|
|
script_content=s.content,
|
||
|
|
script_type=s.script_type,
|
||
|
|
flow_id=0,
|
||
|
|
step_index=0,
|
||
|
|
)
|
||
|
|
command_task_id = e_db.CommandTask.create(e_task)
|
||
|
|
e_task.id = command_task_id
|
||
|
|
if not isinstance(command_task_id, int) or command_task_id <= 0:
|
||
|
|
return public.return_message(-1, 0,"Task creation failed:" + command_task_id)
|
||
|
|
|
||
|
|
log_list = []
|
||
|
|
for i in nodes:
|
||
|
|
elog = CommandLog(
|
||
|
|
command_task_id=command_task_id,
|
||
|
|
server_id=i["id"],
|
||
|
|
ssh_host=i["ssh_conf"]["host"],
|
||
|
|
status=0,
|
||
|
|
log_name=i["log_name"],
|
||
|
|
)
|
||
|
|
elog.create_log()
|
||
|
|
log_list.append(elog)
|
||
|
|
|
||
|
|
last_id = e_db.CommandLog.create(log_list)
|
||
|
|
if not isinstance(last_id, int) or last_id <= 0:
|
||
|
|
for i in log_list:
|
||
|
|
i.remove_log()
|
||
|
|
return public.return_message(-1, 0,"Failed to create log:" + last_id)
|
||
|
|
|
||
|
|
script_py = "{}/script/node_command_executor.py command".format(public.get_panel_path())
|
||
|
|
res = public.ExecShell("nohup {} {} {} > /dev/null 2>&1 &".format(
|
||
|
|
public.get_python_bin(), script_py, command_task_id)
|
||
|
|
)
|
||
|
|
|
||
|
|
data_dict = e_task.to_dict()
|
||
|
|
data_dict["log_list"] = [i.to_dict() for i in log_list]
|
||
|
|
data_dict["task_id"] = command_task_id
|
||
|
|
# return json_response(status=True, msg="Created successfully", data=data_dict)
|
||
|
|
return public.return_message(0, 0, data_dict)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def get_server_info(server_id: int, server_cache) -> dict:
|
||
|
|
server_info = server_cache.get(server_id)
|
||
|
|
if not server_info:
|
||
|
|
server = ServerNodeDB().get_node_by_id(server_id)
|
||
|
|
if not server:
|
||
|
|
server_cache[server_id] = {}
|
||
|
|
else:
|
||
|
|
server_cache[server_id] = server
|
||
|
|
return server_cache[server_id]
|
||
|
|
else:
|
||
|
|
return server_info
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def get_task_list(cls, get):
|
||
|
|
page_num = max(int(get.get('p/d', 1)), 1)
|
||
|
|
limit = max(int(get.get('limit/d', 16)), 1)
|
||
|
|
script_type = get.get('script_type/s', "all")
|
||
|
|
if not script_type in ["all", "python", "shell"]:
|
||
|
|
script_type = "all"
|
||
|
|
search = get.get('search', "").strip()
|
||
|
|
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
count, tasks = e_db.CommandTask.query_tasks(
|
||
|
|
page=page_num, size=limit, script_type=script_type, search=search
|
||
|
|
)
|
||
|
|
|
||
|
|
res = []
|
||
|
|
server_cache: Dict[int, Dict] = {}
|
||
|
|
for i in tasks:
|
||
|
|
task_dict = i.to_dict()
|
||
|
|
log_list = e_db.CommandLog.query("command_task_id = ?", (i.id,))
|
||
|
|
task_dict["log_list"] = []
|
||
|
|
if i.script_id > 0:
|
||
|
|
s = e_db.Script.find("id = ?", (i.script_id,))
|
||
|
|
if s:
|
||
|
|
task_dict["script_name"] = s.name
|
||
|
|
else:
|
||
|
|
task_dict["script_name"] = "-"
|
||
|
|
|
||
|
|
for j in log_list:
|
||
|
|
tmp = j.to_dict()
|
||
|
|
tmp["server_name"] = cls.get_server_info(j.server_id, server_cache).get("remarks")
|
||
|
|
task_dict["log_list"].append(tmp)
|
||
|
|
|
||
|
|
res.append(task_dict)
|
||
|
|
|
||
|
|
page = public.get_page(count, page_num, limit)
|
||
|
|
page["data"] = res
|
||
|
|
return public.return_message(0, 0,page)
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def get_task_info(cls, get):
|
||
|
|
task_id = get.get('task_id/d', 0)
|
||
|
|
if not task_id:
|
||
|
|
return public.return_message(-1, 0,"Task ID cannot be empty")
|
||
|
|
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
task = e_db.CommandTask.find("id = ?", (task_id,))
|
||
|
|
if not task:
|
||
|
|
return public.return_message(-1, 0,"Task does not exist")
|
||
|
|
|
||
|
|
task_dict = task.to_dict()
|
||
|
|
task_dict["log_list"] = []
|
||
|
|
server_cache = {}
|
||
|
|
log_list = e_db.CommandLog.query("command_task_id = ?", (task_id,))
|
||
|
|
for i in log_list:
|
||
|
|
tmp = i.to_dict()
|
||
|
|
if i.status != 0:
|
||
|
|
tmp["log"] = i.get_log()
|
||
|
|
tmp["server_name"] = cls.get_server_info(i.server_id, server_cache).get("remarks", "")
|
||
|
|
task_dict["log_list"].append(tmp)
|
||
|
|
|
||
|
|
return public.return_message(0, 0,task_dict)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def delete_task(get):
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
task_id = get.get('task_id/d', 0)
|
||
|
|
if not task_id:
|
||
|
|
return public.return_message(-1, 0,"Task ID cannot be empty")
|
||
|
|
|
||
|
|
task = e_db.CommandTask.find("id = ?", (task_id,))
|
||
|
|
if not task:
|
||
|
|
return public.return_message(-1, 0,"Task does not exist")
|
||
|
|
|
||
|
|
pid_file = "{}/logs/executor_log/{}.pid".format(public.get_panel_path(), task_id)
|
||
|
|
if os.path.exists(pid_file):
|
||
|
|
pid: str = public.readFile(pid_file)
|
||
|
|
if pid and pid.isdigit():
|
||
|
|
public.ExecShell("kill -9 {}".format(pid))
|
||
|
|
os.remove(pid_file)
|
||
|
|
|
||
|
|
log_list = e_db.CommandLog.query("command_task_id = ?", (task_id,))
|
||
|
|
for i in log_list:
|
||
|
|
i.remove_log()
|
||
|
|
e_db.CommandLog.delete(i.id)
|
||
|
|
|
||
|
|
e_db.CommandTask.delete(task_id)
|
||
|
|
return public.return_message(0, 0,"Deleted successfully")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def batch_delete_task(get):
|
||
|
|
task_ids: List[int] = list_args(get, "task_ids")
|
||
|
|
if not task_ids:
|
||
|
|
return public.return_message(-1, 0,"Please select the task to delete")
|
||
|
|
task_ids = [int(i) for i in task_ids]
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
task_list = e_db.CommandTask.query("id IN ({})".format(",".join(["?"] * len(task_ids))), (*task_ids,))
|
||
|
|
if not task_list:
|
||
|
|
return public.return_message(-1, 0,"Task does not exist")
|
||
|
|
for i in task_list:
|
||
|
|
pid_file = "{}/logs/executor_log/{}.pid".format(public.get_panel_path(), i.id)
|
||
|
|
if os.path.exists(pid_file):
|
||
|
|
pid: str = public.readFile(pid_file)
|
||
|
|
if pid and pid.isdigit():
|
||
|
|
public.ExecShell("kill -9 {}".format(pid))
|
||
|
|
os.remove(pid_file)
|
||
|
|
|
||
|
|
log_list = e_db.CommandLog.query("command_task_id = ?", (i.id,))
|
||
|
|
for j in log_list:
|
||
|
|
j.remove_log()
|
||
|
|
e_db.CommandLog.delete(j.id)
|
||
|
|
e_db.CommandTask.delete(i.id)
|
||
|
|
|
||
|
|
return public.return_message(0, 0,"Deleted successfully")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def retry_task(get):
|
||
|
|
task_id = get.get('task_id/d', 0)
|
||
|
|
if not task_id:
|
||
|
|
return public.return_message(-1, 0,"Task ID cannot be empty")
|
||
|
|
|
||
|
|
log_id = get.get('log_id/d', 0)
|
||
|
|
if not log_id:
|
||
|
|
return public.return_message(-1, 0,"The log ID cannot be empty")
|
||
|
|
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
log = e_db.CommandLog.find("id = ? AND command_task_id = ?", (log_id, task_id))
|
||
|
|
if not log:
|
||
|
|
return public.return_message(-1, 0,"log does not exist")
|
||
|
|
|
||
|
|
log.create_log()
|
||
|
|
log.status = 0
|
||
|
|
e_db.CommandLog.update(log)
|
||
|
|
script_py = "{}/script/node_command_executor.py command".format(public.get_panel_path())
|
||
|
|
public.ExecShell("nohup {} {} {} {} > /dev/null 2>&1 &".format(
|
||
|
|
public.get_python_bin(), script_py, task_id, log_id)
|
||
|
|
)
|
||
|
|
return public.return_message(0, 0,"Retry started")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def node_create_transfer_task(get):
|
||
|
|
try:
|
||
|
|
transfer_task_data = json.loads(get.get('transfer_task_data', "{}"))
|
||
|
|
if not transfer_task_data:
|
||
|
|
return public.return_message(-1, 0,"Parameter error")
|
||
|
|
except Exception as e:
|
||
|
|
return public.return_message(-1, 0,"Parameter error")
|
||
|
|
|
||
|
|
transfer_task_data["flow_id"] = 0
|
||
|
|
transfer_task_data["step_index"] = 0
|
||
|
|
transfer_task_data["src_node"] = {"name": "local"}
|
||
|
|
transfer_task_data["src_node_task_id"] = 0
|
||
|
|
if not isinstance(transfer_task_data["dst_nodes"], dict):
|
||
|
|
return public.return_message(-1, 0,"Please upgrade the version of the main node panel you are currently using")
|
||
|
|
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
tt = TransferTask.from_dict(transfer_task_data)
|
||
|
|
task_id = fdb.TransferTask.create(tt)
|
||
|
|
if not task_id:
|
||
|
|
return public.return_message(-1, 0,"Task creation failed")
|
||
|
|
# return json_response(status=True, msg="Created successfully", data={"task_id": task_id})
|
||
|
|
return public.return_message(0, 0, {"task_id": task_id})
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def node_transferfile_status_history(cls, get):
|
||
|
|
task_id = get.get('task_id/d', 0)
|
||
|
|
only_error = get.get('only_error/d', 1)
|
||
|
|
if not task_id:
|
||
|
|
return public.return_message(-1, 0,"Task ID cannot be empty")
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
ret = fdb.history_transferfile_task(task_id, only_error=only_error==1)
|
||
|
|
fdb.close()
|
||
|
|
# return json_response(status=True, msg="Successfully obtained", data=ret)
|
||
|
|
return public.return_message(0, 0, ret)
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def node_proxy_transferfile_status(cls, get):
|
||
|
|
ws: simple_websocket.Server = getattr(get, '_ws', None)
|
||
|
|
if not ws:
|
||
|
|
return public.return_message(-1, 0, "Please use WebSocket connection")
|
||
|
|
|
||
|
|
task_id = get.get('task_id/d', 0)
|
||
|
|
exclude_nodes = list_args(get, "exclude_nodes")
|
||
|
|
the_log_id = get.get('the_log_id/d', 0)
|
||
|
|
if not task_id:
|
||
|
|
ws.send(json.dumps({"type": "end", "msg": "Task ID cannot be empty"}))
|
||
|
|
ws.send("{}")
|
||
|
|
return
|
||
|
|
|
||
|
|
try:
|
||
|
|
exclude_nodes = [int(i) for i in exclude_nodes]
|
||
|
|
except:
|
||
|
|
exclude_nodes = []
|
||
|
|
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
task = fdb.TransferTask.get_byid(task_id)
|
||
|
|
if not task:
|
||
|
|
ws.send(json.dumps({"type": "end", "msg": "Task does not exist"}))
|
||
|
|
ws.send("{}")
|
||
|
|
return
|
||
|
|
if the_log_id: # 单任务重试
|
||
|
|
res_data = file_task_run_sync(task_id, the_log_id)
|
||
|
|
if isinstance(res_data, str):
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": res_data}))
|
||
|
|
ws.send("{}")
|
||
|
|
else:
|
||
|
|
ws.send(json.dumps({"type": "end", "data": res_data}))
|
||
|
|
ws.send("{}")
|
||
|
|
fdb.close()
|
||
|
|
return
|
||
|
|
|
||
|
|
if task.status in (0, 3): # 初次执行 或 出错后再次尝试
|
||
|
|
pid = cls._start_task("file", task_id, exclude_nodes=exclude_nodes)
|
||
|
|
elif task.status == 2: # 运行成功了, 获取历史数据并返回
|
||
|
|
ret = fdb.history_transferfile_task(task_id)
|
||
|
|
ws.send(json.dumps({"type": "end", "data": ret}))
|
||
|
|
ws.send("{}")
|
||
|
|
fdb.close()
|
||
|
|
return
|
||
|
|
else: # 还在运行中
|
||
|
|
pid_file = "{}/logs/executor_log/file_{}_0.pid".format(public.get_panel_path(), task_id)
|
||
|
|
if os.path.exists(pid_file):
|
||
|
|
pid = int(public.readFile(pid_file))
|
||
|
|
else:
|
||
|
|
pid = None
|
||
|
|
|
||
|
|
if not pid: # 运行失败, 返回数据库信息
|
||
|
|
ret = fdb.history_transferfile_task(task_id)
|
||
|
|
ws.send(json.dumps({"type": "end", "data": ret}))
|
||
|
|
fdb.close()
|
||
|
|
ws.send("{}")
|
||
|
|
return
|
||
|
|
|
||
|
|
def send_status(soc_data: dict):
|
||
|
|
ws.send(json.dumps({"type": "status", "data": soc_data}))
|
||
|
|
|
||
|
|
err = self_file_running_log(task_id, send_status)
|
||
|
|
if err:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": err}))
|
||
|
|
|
||
|
|
ret = fdb.history_transferfile_task(task_id)
|
||
|
|
ws.send(json.dumps({"type": "end", "data": ret}))
|
||
|
|
fdb.close()
|
||
|
|
ws.send("{}") # 告诉接收端,数据传输已经结束
|
||
|
|
return
|
||
|
|
|
||
|
|
def run_flow_task(self, get):
|
||
|
|
ws: simple_websocket.Server = getattr(get, '_ws', None)
|
||
|
|
if not ws:
|
||
|
|
return public.return_message(-1, 0,"Please use WebSocket connection")
|
||
|
|
|
||
|
|
public.set_module_logs("nodes_flow_task", "run_flow_task")
|
||
|
|
node_ids = list_args(get, 'node_ids')
|
||
|
|
if not node_ids:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "Node ID cannot be empty"}))
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
node_ids = [int(i) for i in node_ids]
|
||
|
|
except:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "Node ID format error"}))
|
||
|
|
return
|
||
|
|
|
||
|
|
try:
|
||
|
|
flow_data = get.get('flow_data', '[]')
|
||
|
|
if isinstance(flow_data, str):
|
||
|
|
flow_data = json.loads(flow_data)
|
||
|
|
elif isinstance(flow_data, (list, tuple)):
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
raise
|
||
|
|
except:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "Process data format error"}))
|
||
|
|
return
|
||
|
|
|
||
|
|
strategy = {"run_when_error": True}
|
||
|
|
if "exclude_when_error" in get and get.exclude_when_error not in ("1", "true", 1, True):
|
||
|
|
strategy["exclude_when_error"] = False
|
||
|
|
|
||
|
|
has_cmd_task = False
|
||
|
|
data_src_node = []
|
||
|
|
for i in flow_data:
|
||
|
|
if i["task_type"] == "command":
|
||
|
|
has_cmd_task = True
|
||
|
|
elif i["task_type"] == "file":
|
||
|
|
data_src_node.append(i["src_node_id"])
|
||
|
|
|
||
|
|
nodes_db = ServerNodeDB()
|
||
|
|
used_nodes, target_nodes = [], []
|
||
|
|
srv_cache = ServerMonitorRepo()
|
||
|
|
for i in set(node_ids + data_src_node):
|
||
|
|
n = nodes_db.get_node_by_id(i)
|
||
|
|
if not n:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "The node with node ID [{}] does not exist".format(i)}))
|
||
|
|
return
|
||
|
|
n["ssh_conf"] = json.loads(n["ssh_conf"])
|
||
|
|
if has_cmd_task and n["id"] in node_ids and not n["ssh_conf"]:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "The node of node [{}] has not enabled SSH".format(n["remarks"])}))
|
||
|
|
return
|
||
|
|
if n["id"] in data_src_node:
|
||
|
|
is_local = n["app_key"] == n["api_key"] == "local"
|
||
|
|
if (not n["app_key"] and not n["api_key"]) or n["lpver"]: # 1panel面板或者 仅有ssh配置的节点无法作为数据源
|
||
|
|
ws.send(json.dumps(
|
||
|
|
{"type": "error", "msg": "Node [{}] is not a pagoda node and cannot be used as a data source".format(n["remarks"])}))
|
||
|
|
return
|
||
|
|
if not is_local:
|
||
|
|
# 检查节点版本号
|
||
|
|
tmp = srv_cache.get_server_status(n["id"])
|
||
|
|
if not tmp or not flow_useful_version(tmp["version"]):
|
||
|
|
ws.send(
|
||
|
|
json.dumps({"type": "error", "msg": "Node [{}] version is too low, please upgrade the node".format(n["remarks"])}))
|
||
|
|
return
|
||
|
|
|
||
|
|
used_nodes.append(n)
|
||
|
|
if n["id"] in node_ids:
|
||
|
|
target_nodes.append(n)
|
||
|
|
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
flow, err = fdb.create_flow(used_nodes, target_nodes, strategy, flow_data)
|
||
|
|
if not flow:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": err}))
|
||
|
|
return
|
||
|
|
fdb.close()
|
||
|
|
|
||
|
|
pid = self._start_task("flow", flow.id)
|
||
|
|
if not pid:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "Task startup failed"}))
|
||
|
|
return
|
||
|
|
|
||
|
|
def update_status(data: dict):
|
||
|
|
ws.send(json.dumps({"type": "status", "data": data}))
|
||
|
|
|
||
|
|
err = flow_running_log(flow.id, update_status)
|
||
|
|
if err:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": err}))
|
||
|
|
# flow_data = fdb.history_flow_task(flow.id)
|
||
|
|
# ws.send(json.dumps({"type": "data", "data": flow_data}))
|
||
|
|
ws.send(json.dumps({"type": "end", "msg": "Mission complete"}))
|
||
|
|
return
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def _start_task(cls, task_type: str, task_id: int, exclude_nodes: List[int]=None) -> Optional[int]:
|
||
|
|
pid_file = "{}/logs/executor_log/{}_{}_0.pid".format(public.get_panel_path(), task_type, task_id)
|
||
|
|
if os.path.exists(pid_file):
|
||
|
|
pid = int(public.readFile(pid_file))
|
||
|
|
if psutil.pid_exists(pid):
|
||
|
|
return pid
|
||
|
|
|
||
|
|
script_py = "{}/script/node_command_executor.py".format(public.get_panel_path())
|
||
|
|
cmd = [
|
||
|
|
public.get_python_bin(), script_py,
|
||
|
|
"--task_type={}".format(task_type),
|
||
|
|
"--task_id={}".format(task_id),
|
||
|
|
]
|
||
|
|
|
||
|
|
exclude_nodes = exclude_nodes or []
|
||
|
|
if exclude_nodes:
|
||
|
|
exclude_nodes = [str(i) for i in exclude_nodes if i]
|
||
|
|
exclude_nodes_str = "'{}'".format(",".join(exclude_nodes))
|
||
|
|
cmd.append("--exclude_nodes={}".format(exclude_nodes_str))
|
||
|
|
|
||
|
|
cmd_str = "nohup {} > /dev/null 2>&1 &".format(" ".join(cmd))
|
||
|
|
public.ExecShell(cmd_str)
|
||
|
|
for i in range(60):
|
||
|
|
if os.path.exists(pid_file):
|
||
|
|
pid = int(public.readFile(pid_file))
|
||
|
|
if psutil.pid_exists(pid):
|
||
|
|
return pid
|
||
|
|
time.sleep(0.05)
|
||
|
|
return None
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def flow_task_status(cls, get):
|
||
|
|
ws: simple_websocket.Server = getattr(get, '_ws', None)
|
||
|
|
if not ws:
|
||
|
|
return public.return_message(-1, 0, "Please use WebSocket connection")
|
||
|
|
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
flow = fdb.Flow.last(order_by="id DESC")
|
||
|
|
if flow and flow.status == "running":
|
||
|
|
flow_data = fdb.history_flow_task(flow)
|
||
|
|
ws.send(json.dumps({"type": "status", "data": flow_data}))
|
||
|
|
for t in flow.steps:
|
||
|
|
t: Union[CommandTask, TransferTask]
|
||
|
|
src_node = getattr(t, "src_node", {})
|
||
|
|
is_local_src = src_node.get("address", None) is None
|
||
|
|
if not src_node:
|
||
|
|
task_data = fdb.history_command_task(t.id)
|
||
|
|
elif is_local_src:
|
||
|
|
task_data = fdb.history_transferfile_task(t.id)
|
||
|
|
else:
|
||
|
|
srv = ServerNode(src_node["address"], src_node["api_key"], src_node["app_key"], src_node["remarks"])
|
||
|
|
srv_data = srv.node_transferfile_status_history(t.src_node_task_id)
|
||
|
|
if srv_data["status"]:
|
||
|
|
task_data = srv_data["data"]
|
||
|
|
else:
|
||
|
|
task_data = {
|
||
|
|
"task_id": t.id, "task_type": "file",
|
||
|
|
"count": 0, "complete": 0, "error": 0, "data": []
|
||
|
|
}
|
||
|
|
ws.send(json.dumps({"type": "status", "data": task_data}))
|
||
|
|
|
||
|
|
err = flow_running_log(flow.id, lambda x: ws.send(json.dumps({"type": "status", "data": x})))
|
||
|
|
if err:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": err}))
|
||
|
|
|
||
|
|
ws.send(json.dumps({"type": "end", "msg": "Mission complete"}))
|
||
|
|
return
|
||
|
|
else:
|
||
|
|
if not flow:
|
||
|
|
ws.send(json.dumps({"type": "no_flow", "msg": "No tasks"})) # 没有任务
|
||
|
|
return
|
||
|
|
flow_data = fdb.history_flow_task(flow.id)
|
||
|
|
ws.send(json.dumps({"type": "end", "last_flow": flow_data}))
|
||
|
|
return
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def next_flow_tip(cls, get):
|
||
|
|
return public.return_message(0, 0,"Setup successful")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def get_flow_info(get):
|
||
|
|
flow_id = get.get("flow_id/d", 0)
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
flow = fdb.Flow.get_byid(flow_id)
|
||
|
|
if not flow:
|
||
|
|
return public.return_message(-1, 0,"Task does not exist")
|
||
|
|
|
||
|
|
flow_data = fdb.history_flow_task(flow.id)
|
||
|
|
return public.return_message(0, 0,flow_data)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def get_command_task_info(get):
|
||
|
|
task_id = get.get("task_id/d", 0)
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
task = fdb.CommandTask.get_byid(task_id)
|
||
|
|
if not task:
|
||
|
|
return public.return_message(-1, 0,"Task does not exist")
|
||
|
|
return public.return_message(0, 0,fdb.history_command_task(task.id, only_error=False))
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def get_transferfile_task_info(get):
|
||
|
|
task_id = get.get("task_id/d", 0)
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
task = fdb.TransferTask.get_byid(task_id)
|
||
|
|
if not task:
|
||
|
|
return public.return_message(-1, 0,"Task does not exist")
|
||
|
|
|
||
|
|
src_node = task.src_node
|
||
|
|
is_local_src = task.src_node.get("address", None) is None
|
||
|
|
if is_local_src:
|
||
|
|
return public.return_message(0, 0,fdb.history_transferfile_task(task.id, only_error=False))
|
||
|
|
else:
|
||
|
|
srv = ServerNode(src_node["address"], src_node["api_key"], src_node["app_key"], src_node["name"])
|
||
|
|
srv_data = srv.node_transferfile_status_history(task.src_node_task_id, only_error=False)
|
||
|
|
if srv_data["status"]:
|
||
|
|
task_data = srv_data["data"]
|
||
|
|
else:
|
||
|
|
task_data = {
|
||
|
|
"task_id": task.id, "task_type": "file",
|
||
|
|
"count": 0, "complete": 0, "error": 0, "data": []
|
||
|
|
}
|
||
|
|
return public.return_message(0, 0,task_data)
|
||
|
|
|
||
|
|
def flow_task_list(self, get):
|
||
|
|
page_num = max(int(get.get('p/d', 1)), 1)
|
||
|
|
limit = max(int(get.get('limit/d', 16)), 1)
|
||
|
|
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
flow_list = fdb.Flow.query_page(page_num=page_num, limit=limit)
|
||
|
|
count = fdb.Flow.count()
|
||
|
|
res = []
|
||
|
|
server_cache: Dict[int, Dict] = {}
|
||
|
|
for flow in flow_list:
|
||
|
|
tmp_data = fdb.history_flow_task(flow.id)
|
||
|
|
tmp_data["server_list"] = [{
|
||
|
|
"id": int(i),
|
||
|
|
"name": self.get_server_info(int(i), server_cache).get("remarks", ""),
|
||
|
|
"server_ip": self.get_server_info(int(i), server_cache).get("server_ip", ""),
|
||
|
|
} for i in tmp_data["server_ids"].strip("|").split("|")]
|
||
|
|
res.append(tmp_data)
|
||
|
|
|
||
|
|
page = public.get_page(count, page_num, limit)
|
||
|
|
page["data"] = res
|
||
|
|
return public.return_message(0, 0,page)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def remove_flow(get):
|
||
|
|
flow_ids = list_args(get,"flow_ids")
|
||
|
|
if not flow_ids:
|
||
|
|
return public.return_message(-1, 0,"Please select the task to delete")
|
||
|
|
fdb = TaskFlowsDB()
|
||
|
|
flows = fdb.Flow.query(
|
||
|
|
"id IN (%s) AND status NOT IN (?, ?)" % (",".join(["?"]*len(flow_ids))),
|
||
|
|
(*flow_ids, "waiting", "running")
|
||
|
|
)
|
||
|
|
|
||
|
|
command_tasks = fdb.CommandTask.query(
|
||
|
|
"flow_id IN (%s)" % (",".join(["?"]*len(flow_ids))),
|
||
|
|
(*flow_ids,)
|
||
|
|
)
|
||
|
|
|
||
|
|
command_logs = fdb.CommandLog.query(
|
||
|
|
"command_task_id IN (%s)" % (",".join(["?"]*len(flow_ids))),
|
||
|
|
(*flow_ids,)
|
||
|
|
)
|
||
|
|
|
||
|
|
for log in command_logs:
|
||
|
|
try:
|
||
|
|
if os.path.exists(log.log_file):
|
||
|
|
os.remove(log.log_file)
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
fdb.CommandLog.delete([log.id for log in command_logs])
|
||
|
|
fdb.CommandTask.delete([task.id for task in command_tasks])
|
||
|
|
fdb.Flow.delete([flow.id for flow in flows])
|
||
|
|
|
||
|
|
w, p = "flow_id IN (%s)" % (",".join(["?"]*len(flow_ids))), (*flow_ids,)
|
||
|
|
fdb.TransferTask.delete_where(w, p)
|
||
|
|
fdb.TransferLog.delete_where(w, p)
|
||
|
|
fdb.TransferFile.delete_where(w, p)
|
||
|
|
|
||
|
|
return public.return_message(0, 0,"Deleted successfully")
|
||
|
|
|
||
|
|
def retry_flow(self, get):
|
||
|
|
ws: simple_websocket.Server = getattr(get, '_ws', None)
|
||
|
|
if not ws:
|
||
|
|
return public.return_message(-1, 0,"Please use WebSocket connection")
|
||
|
|
|
||
|
|
flow_id = get.get("flow_id/d", 0)
|
||
|
|
flow = TaskFlowsDB().Flow.get_byid(flow_id)
|
||
|
|
if not flow:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "Task does not exist"}))
|
||
|
|
return
|
||
|
|
|
||
|
|
if flow.status == "complete":
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "Task completed, cannot retry"}))
|
||
|
|
return
|
||
|
|
|
||
|
|
def call_status(data):
|
||
|
|
ws.send(json.dumps({"type": "status", "data": data}))
|
||
|
|
|
||
|
|
pid = self._start_task("flow", flow.id)
|
||
|
|
if not pid:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": "Task startup failed"}))
|
||
|
|
return
|
||
|
|
|
||
|
|
err = flow_running_log(flow.id, call_status)
|
||
|
|
if err:
|
||
|
|
ws.send(json.dumps({"type": "error", "msg": err}))
|
||
|
|
|
||
|
|
ws.send(json.dumps({"type": "end", "msg": "Mission complete"}))
|
||
|
|
return
|
||
|
|
|
||
|
|
# 重试某个单一任务, 如:单机器文件上传或单机器命令执行
|
||
|
|
@staticmethod
|
||
|
|
def retry_flow_task(get):
|
||
|
|
task_type = get.get("task_type/s", "")
|
||
|
|
task_id = get.get("task_id/d", 0)
|
||
|
|
log_id = get.get("log_id/d", 0)
|
||
|
|
if not task_type or not task_id or not log_id:
|
||
|
|
return public.return_message(-1, 0,"Parameter error")
|
||
|
|
if task_type not in ("file", "command"):
|
||
|
|
return public.return_message(-1, 0,"Parameter error")
|
||
|
|
if task_type == "file":
|
||
|
|
ret = file_task_run_sync(task_id, log_id)
|
||
|
|
else:
|
||
|
|
ret = command_task_run_sync(task_id, log_id)
|
||
|
|
if isinstance(ret, str):
|
||
|
|
return public.return_message(-1, 0,ret)
|
||
|
|
# return json_response(status=True, msg="Task has been retried", data=ret)
|
||
|
|
return public.return_message(0, 0, ret)
|
||
|
|
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def stop_flow(get):
|
||
|
|
flow_id = get.get("flow_id/d", 0)
|
||
|
|
if not flow_id:
|
||
|
|
return public.return_message(-1, 0,"Please select the task to stop")
|
||
|
|
pid_file = "{}/logs/executor_log/flow_{}_0.pid".format(public.get_panel_path(), flow_id)
|
||
|
|
if os.path.exists(pid_file):
|
||
|
|
pid = int(public.readFile(pid_file))
|
||
|
|
if psutil.pid_exists(pid):
|
||
|
|
psutil.Process(pid).kill()
|
||
|
|
|
||
|
|
if os.path.exists(pid_file):
|
||
|
|
os.remove(pid_file)
|
||
|
|
|
||
|
|
sock_file = "/tmp/flow_task/flow_task_{}".format(flow_id)
|
||
|
|
if os.path.exists(sock_file):
|
||
|
|
os.remove(sock_file)
|
||
|
|
|
||
|
|
return public.return_message(0, 0,"Task stopped")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def file_dstpath_check(get):
|
||
|
|
path = get.get("path/s", "")
|
||
|
|
node_ids = list_args(get, "node_ids")
|
||
|
|
if not path or not node_ids:
|
||
|
|
return public.return_message(-1, 0,"Parameter error")
|
||
|
|
|
||
|
|
if path == "/":
|
||
|
|
return public.return_message(-1, 0,"Cannot upload to root directory")
|
||
|
|
|
||
|
|
nodes_db = ServerNodeDB()
|
||
|
|
ret = []
|
||
|
|
|
||
|
|
def check_node(n_data:dict, t_srv: Union[ServerNode, LPanelNode, SSHApi]):
|
||
|
|
res = {"id": n_data["id"], "err": "", "remarks": n_data["remarks"]}
|
||
|
|
err = t_srv.upload_dir_check(path)
|
||
|
|
if err:
|
||
|
|
res["err"] = err
|
||
|
|
ret.append(res)
|
||
|
|
|
||
|
|
th_list = []
|
||
|
|
for i in node_ids:
|
||
|
|
n = nodes_db.get_node_by_id(i)
|
||
|
|
if not n:
|
||
|
|
ret.append({"id": i, "err": "Node does not exist"})
|
||
|
|
n["ssh_conf"] = json.loads(n["ssh_conf"])
|
||
|
|
if n["app_key"] or n["api_key"]:
|
||
|
|
srv = ServerNode.new_by_data(n)
|
||
|
|
elif n["ssh_conf"]:
|
||
|
|
srv = SSHApi(**n["ssh_conf"])
|
||
|
|
else:
|
||
|
|
ret.append({"id": i, "err": "Node configuration error"})
|
||
|
|
continue
|
||
|
|
|
||
|
|
th = threading.Thread(target=check_node, args=(n, srv))
|
||
|
|
th.start()
|
||
|
|
th_list.append(th)
|
||
|
|
|
||
|
|
for th in th_list:
|
||
|
|
th.join()
|
||
|
|
|
||
|
|
# return json_response(status=True, data=ret)
|
||
|
|
return public.return_message(0, 0,ret)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def create_flow_template(get):
|
||
|
|
err = FlowTemplates.check(get)
|
||
|
|
if err:
|
||
|
|
return public.return_message(-1, 0,err)
|
||
|
|
s = FlowTemplates.from_dict(get)
|
||
|
|
# 查重
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
if e_db.FlowTemplate.find("name = ?", (s.name,)):
|
||
|
|
return public.return_message(-1, 0,"Script name already exists")
|
||
|
|
err = e_db.FlowTemplate.create(s)
|
||
|
|
if isinstance(err, str):
|
||
|
|
return public.return_message(-1, 0,err)
|
||
|
|
e_db.close()
|
||
|
|
# return json_response(status=True, msg="Created successfully", data=s.to_dict())
|
||
|
|
return public.return_message(0, 0,s.to_dict())
|
||
|
|
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def modify_flow_template(get):
|
||
|
|
err = FlowTemplates.check(get)
|
||
|
|
if err:
|
||
|
|
return public.return_message(-1, 0,err)
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
ft = FlowTemplates.from_dict(get)
|
||
|
|
if not ft.id:
|
||
|
|
return public.return_message(-1, 0,"Please select the template to modify")
|
||
|
|
if not e_db.FlowTemplate.get_byid(ft.id):
|
||
|
|
return public.return_message(-1, 0,"Template does not exist")
|
||
|
|
err = e_db.FlowTemplate.update(ft)
|
||
|
|
if isinstance(err, str) and err:
|
||
|
|
return public.return_message(-1, 0,err)
|
||
|
|
e_db.close()
|
||
|
|
# return json_response(status=True, msg="Modified successfully", data=ft.to_dict())
|
||
|
|
return public.return_message(0, 0,ft.to_dict())
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def delete_flow_template(get):
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
if not get.get("id/d", 0):
|
||
|
|
return public.return_message(-1, 0,"Script ID cannot be empty")
|
||
|
|
try:
|
||
|
|
del_id = int(get.id)
|
||
|
|
except:
|
||
|
|
return public.return_message(-1, 0,"Script ID format error")
|
||
|
|
|
||
|
|
e_db.FlowTemplate.delete(del_id)
|
||
|
|
return public.return_message(0, 0,"Deleted successfully")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def get_flow_template_list(get):
|
||
|
|
page_num = max(int(get.get('p/d', 1)), 1)
|
||
|
|
limit = max(int(get.get('limit/d', 16)), 1)
|
||
|
|
search = get.get('search', "").strip()
|
||
|
|
|
||
|
|
where_list, params = [], []
|
||
|
|
if search:
|
||
|
|
where_list.append("(name like ? or key_words like ? or description like ?)")
|
||
|
|
params.append("%{}%".format(search))
|
||
|
|
params.append("%{}%".format(search))
|
||
|
|
params.append("%{}%".format(search))
|
||
|
|
|
||
|
|
where = " and ".join(where_list)
|
||
|
|
e_db = TaskFlowsDB()
|
||
|
|
data_list = e_db.FlowTemplate.query_page(where, (*params,), page_num=page_num, limit=limit)
|
||
|
|
count = e_db.FlowTemplate.count(where, params)
|
||
|
|
page = public.get_page(count, page_num, limit)
|
||
|
|
page["data"] = [i.to_dict() for i in data_list]
|
||
|
|
return public.return_message(0, 0,page)
|
||
|
|
|
||
|
|
|