403 lines
15 KiB
Python
403 lines
15 KiB
Python
#!/usr/bin/python3
|
|
# -*- coding: UTF-8 -*-
|
|
# 脚本插件化执行管理
|
|
# TODO:
|
|
# 使能/失能 --配置文件--ok
|
|
# 错误码 --脚本/本程序--wait/add
|
|
# 进度同步 --线程获取--add
|
|
# 输出规范 --脚本/本程序(重定向日志文件、输出格式)--ok/ok
|
|
# 元数据类型 --描述信息、翻译(配置文件)--ok
|
|
# 运行等级 --(root/user)--wait
|
|
|
|
import subprocess
|
|
import os
|
|
from sre_compile import isstring
|
|
import threading
|
|
import yaml
|
|
import logging
|
|
from gi.repository import GObject
|
|
|
|
class pluginState():
|
|
PLUGIN_SUCCESS = 0
|
|
PLUGINERR_PLUGIN_NOT_EXIST = PLUGIN_SUCCESS - 1
|
|
PLUGINERR_PLUGIN_NOT_COMPLETED = PLUGINERR_PLUGIN_NOT_EXIST - 1
|
|
PLUGINERR_NO_AVAILABLE_YAML = PLUGINERR_PLUGIN_NOT_COMPLETED - 1
|
|
PLUGINERR_NOT_LOAD_ALL = PLUGINERR_NO_AVAILABLE_YAML - 1
|
|
PLUGINERR_PLUGIN_NOT_IN_LIST = PLUGINERR_NOT_LOAD_ALL - 1
|
|
PLUGINERR_PLUGIN_NOT_ENABLED = PLUGINERR_PLUGIN_NOT_IN_LIST - 1
|
|
PLUGINERR_PLUGIN_CONFIG_FAILED = PLUGINERR_PLUGIN_NOT_ENABLED - 1
|
|
PLUGINERR_LOG_PATH_NOT_EXIT = PLUGINERR_PLUGIN_CONFIG_FAILED - 1
|
|
PLUGINERR_CONFIG_NOT_COMPLETED = PLUGINERR_LOG_PATH_NOT_EXIT - 1
|
|
PLUGINERR_CMD_IS_NONE = PLUGINERR_CONFIG_NOT_COMPLETED - 1
|
|
PLUGINERR_LANGUAGE_NOT_SUPPORT = PLUGINERR_CMD_IS_NONE - 1
|
|
|
|
_numToInfo = {
|
|
PLUGIN_SUCCESS: 'success',
|
|
PLUGINERR_PLUGIN_NOT_EXIST: 'plugin path not exist',
|
|
PLUGINERR_PLUGIN_NOT_COMPLETED: 'plugin folder not completed',
|
|
PLUGINERR_NO_AVAILABLE_YAML: 'there is no available yaml',
|
|
PLUGINERR_NOT_LOAD_ALL: 'not run load_all',
|
|
PLUGINERR_PLUGIN_NOT_IN_LIST: 'plugin not in loaded plugin list',
|
|
PLUGINERR_PLUGIN_NOT_ENABLED: 'plugin not enabled',
|
|
PLUGINERR_PLUGIN_CONFIG_FAILED: 'plugin config failed',
|
|
PLUGINERR_LOG_PATH_NOT_EXIT: 'log path not exists',
|
|
PLUGINERR_CONFIG_NOT_COMPLETED: 'config not completed',
|
|
PLUGINERR_CMD_IS_NONE: 'cmd is none',
|
|
PLUGINERR_LANGUAGE_NOT_SUPPORT: 'not support this language',
|
|
}
|
|
_infoToNum = {
|
|
'success': PLUGIN_SUCCESS,
|
|
'plugin path not exist': PLUGINERR_PLUGIN_NOT_EXIST,
|
|
'plugin folder not completed': PLUGINERR_PLUGIN_NOT_COMPLETED,
|
|
'there is no available yaml': PLUGINERR_NO_AVAILABLE_YAML,
|
|
'not run load_all': PLUGINERR_NOT_LOAD_ALL,
|
|
'plugin not in loaded plugin list': PLUGINERR_PLUGIN_NOT_IN_LIST,
|
|
'plugin not enabled': PLUGINERR_PLUGIN_NOT_ENABLED,
|
|
'plugin config failed': PLUGINERR_PLUGIN_CONFIG_FAILED,
|
|
'log path not exists': PLUGINERR_LOG_PATH_NOT_EXIT,
|
|
'config not completed': PLUGINERR_CONFIG_NOT_COMPLETED,
|
|
'cmd is none': PLUGINERR_CMD_IS_NONE,
|
|
'not support this language': PLUGINERR_LANGUAGE_NOT_SUPPORT,
|
|
}
|
|
|
|
|
|
PLUGIN_MANAGER_PATH = "./" # 可修改
|
|
|
|
# 目录结构 FILE PATH
|
|
CFG_FILE = "conf.yaml"
|
|
CFG_EX_DIR = "conf.d/"
|
|
CFG_PATH = PLUGIN_MANAGER_PATH + CFG_FILE
|
|
CFG_EX_PATH = PLUGIN_MANAGER_PATH + CFG_EX_DIR
|
|
MOD_DIR = "modules/"
|
|
MOD_PATH = PLUGIN_MANAGER_PATH + MOD_DIR
|
|
MOD_AVAILABLE = "modules-available/"
|
|
MOD_ENABLED = "modules-enabled/"
|
|
MOD_AVAILABLE_PATH = MOD_PATH + MOD_AVAILABLE
|
|
MOD_ENABLED_PATH = MOD_PATH + MOD_ENABLED
|
|
PLUGIN_DIR = "script/"
|
|
PLUGIN_PATH = PLUGIN_MANAGER_PATH + PLUGIN_DIR
|
|
|
|
# 配置 日志路径
|
|
LOG_DIR_ROOT = '/var/log/kylin-system-updater/'
|
|
# 默认插件日志路径
|
|
PLUGIN_LOG_DIR = '/var/log/kylin-system-updater/plugin/'
|
|
|
|
# PLUGIN.YAML
|
|
PLUGIN_CONF_KEY_NAME = 'name'
|
|
PLUGIN_CONF_KEY_DESC = 'desc'
|
|
PLUGIN_CONF_KEY_EXEC = 'exec'
|
|
PLUGIN_CONF_KEY_LOGLEVEL = 'loglevel'
|
|
PLUGIN_CONF_KEY_RUNLEVEL = 'runlevel'
|
|
PLUGIN_CONF_KEY_LIST = [PLUGIN_CONF_KEY_NAME,PLUGIN_CONF_KEY_DESC,PLUGIN_CONF_KEY_EXEC,PLUGIN_CONF_KEY_LOGLEVEL,PLUGIN_CONF_KEY_RUNLEVEL]
|
|
|
|
# CONF.YAML AND CONF.D
|
|
MANAGER_CONF_KEY_LOGDIR = "logdir"
|
|
MANAGER_CONF_KEY_LIST = [MANAGER_CONF_KEY_LOGDIR, ]
|
|
|
|
|
|
FORMAT = "%(asctime)s [%(levelname)s]: %(message)s"
|
|
RUNLEVEL_LIST = ['ROOT', 'USER']
|
|
LOGLEVEL_LIST = ['DEBUG', 'INFO', 'NOTIFY', 'WARNING', 'ERROR', 'CRITICAL']
|
|
|
|
class LOADSTATE():
|
|
PLGNAME = 0x01
|
|
EXECCMD = 0x02
|
|
STATESUM = PLGNAME + EXECCMD
|
|
|
|
|
|
LANG_KEY_ZH_CN = 'zh_CN'
|
|
LANG_KEY_EN = 'en'
|
|
class LANGLIST():
|
|
LANG_EN = 0
|
|
LANG_ZH_CN = 1
|
|
|
|
|
|
class pluginClass(pluginState):
|
|
|
|
def __init__(self):
|
|
# 必须配置项
|
|
self.pluginName = None
|
|
self.execCmd = None
|
|
# 可选配置项
|
|
self.descList = [] # en / zh
|
|
self.logLevel = LOGLEVEL_LIST.index('DEBUG')
|
|
self.runLevel = RUNLEVEL_LIST.index('ROOT')
|
|
self.enabled = False
|
|
# 合成变量
|
|
self.cmd = None
|
|
self.logDir = PLUGIN_LOG_DIR # 插件日志路径
|
|
self.logPath = os.path.join(self.logDir, "default.log") # 插件日志文件
|
|
self.fifoName = "default-fifo" # 插件进度文件
|
|
# self.fifoPath = PLUGIN_PATH + self.fifoName
|
|
self.fifoPath = "/var/log/kylin-system-updater"+self.fifoName
|
|
# 记录变量
|
|
self.running = False # 是否在运行
|
|
self.process = 0 # 执行进度
|
|
self.loadState = 0 # 插件配置完成
|
|
logging.info("init finished.")
|
|
|
|
###################### 内部函数 ######################
|
|
# 1-配置指定字段
|
|
# 2-更新进度 (1/0.5s)
|
|
# 3-
|
|
######################
|
|
def _config_key(self, cfg, key):
|
|
if cfg == None or key == None or key not in cfg:
|
|
logging.warning("[PLUGIN]: key[%s] not in yaml.", key)
|
|
|
|
if key == PLUGIN_CONF_KEY_NAME:
|
|
if isstring(cfg[key]):
|
|
self.pluginName = cfg[key]
|
|
self.fifoName = cfg[key] + "-fifo"
|
|
self.loadState += LOADSTATE.PLGNAME
|
|
else:
|
|
logging.error("[PLUGIN]: name[%s] not string.", cfg[key])
|
|
|
|
elif key == PLUGIN_CONF_KEY_DESC:
|
|
langList = cfg[key]
|
|
descDict = {}
|
|
if langList == None or len(langList) == 0:
|
|
return
|
|
for i in range(len(langList)):
|
|
descDict = langList[i]
|
|
if LANG_KEY_EN in descDict:
|
|
self.descList.insert(LANGLIST.LANG_EN, descDict.pop(LANG_KEY_EN))
|
|
continue
|
|
elif LANG_KEY_ZH_CN in descDict:
|
|
self.descList.insert(LANGLIST.LANG_ZH_CN, descDict.pop(LANG_KEY_ZH_CN))
|
|
continue
|
|
|
|
elif key == PLUGIN_CONF_KEY_EXEC:
|
|
if isstring(cfg[key]):
|
|
self.execCmd = cfg[key]
|
|
self.loadState += LOADSTATE.EXECCMD
|
|
else:
|
|
logging.error("[PLUGIN]: execCmd[%s] not string.", cfg[key])
|
|
|
|
elif key == PLUGIN_CONF_KEY_LOGLEVEL:
|
|
loglevel = cfg[key].upper()
|
|
if loglevel in LOGLEVEL_LIST:
|
|
self.logLevel = LOGLEVEL_LIST.index(loglevel)
|
|
|
|
elif key == PLUGIN_CONF_KEY_RUNLEVEL:
|
|
runlevel = cfg[key].upper()
|
|
if runlevel in RUNLEVEL_LIST:
|
|
self.runLevel = RUNLEVEL_LIST.index(runlevel)
|
|
else:
|
|
logging.warning("[PLUGIN]: key[%s] not need config.", key)
|
|
|
|
def _update_process(self):
|
|
if not self.running:
|
|
logging.info("[PLUGIN]: plugin [%s] is not running.", self.pluginName)
|
|
return
|
|
|
|
if os.path.exists(self.fifoPath):
|
|
try:
|
|
fd = open(self.fifoPath, 'r', 1)
|
|
process = fd.readline()
|
|
self.process = int(process.strip("\n"))
|
|
except Exception as e:
|
|
logging.info("[PLUGIN]: get process err[%s].",e)
|
|
else:
|
|
logging.info("[PLUGIN]: fifo[%s] not exists.", self.fifoPath)
|
|
|
|
if self.process >= 100 or self.process < 0:
|
|
return
|
|
|
|
tmptimer = threading.Timer(0.5, function=self._update_process)
|
|
tmptimer.start()
|
|
|
|
###################### 外部函数 ######################
|
|
# 1-读取配置文件,并配置该插件
|
|
# 2-使能插件
|
|
# 3-失能插件
|
|
# 4-获取插件名称
|
|
# 5-获取进度
|
|
# 6-注册进度跟新回调
|
|
# 7-执行插件
|
|
# 8-获取描述信息
|
|
# 9-设置脚本日志路径
|
|
|
|
# TODO:
|
|
# 重配置该插件
|
|
######################
|
|
|
|
# 配置该插件
|
|
def plg_config(self, filePath):
|
|
if not os.path.exists(filePath):
|
|
logging.error("[PLUGIN]: [%s] not exist.", filePath)
|
|
return self.PLUGINERR_PLUGIN_CONFIG_FAILED
|
|
|
|
def plg_enable(self):
|
|
self.enabled = True
|
|
def plg_disable(self):
|
|
self.enabled = False
|
|
|
|
def plg_get_name(self):
|
|
return self.pluginName
|
|
|
|
def plg_get_process(self):
|
|
return self.process
|
|
|
|
def plg_get_desc(self):
|
|
# 获得语言变量
|
|
#TODO: 例如:中文繁体,如果不存在的话,显示中文简体
|
|
lang=os.getenv("LANG")
|
|
if LANG_KEY_EN in lang:
|
|
if len(self.descList) > LANGLIST.LANG_EN:
|
|
return self.descList[LANGLIST.LANG_EN]
|
|
else:
|
|
logging.error("[PLUGIN]: There is not a desc of the language[%s].", lang)
|
|
elif LANG_KEY_ZH_CN in lang:
|
|
if len(self.descList) > LANGLIST.LANG_ZH_CN:
|
|
return self.descList[LANGLIST.LANG_ZH_CN]
|
|
else:
|
|
logging.error("[PLUGIN]: There is not a desc of the language[%s].", lang)
|
|
else:
|
|
logging.error("[PLUGIN]: There is not a desc of the language[%s].", lang)
|
|
return
|
|
|
|
# 添加 update cmd
|
|
# 设置脚本日志路径
|
|
def plg_set_logDir(self, logPath):
|
|
if not os.path.exists(logPath):
|
|
try:
|
|
os.makedirs(logPath, mode=0o755)
|
|
except Exception as e:
|
|
logging.error("[PLUGIN]: create plugin log dir failed.[%s]", e)
|
|
return self.PLUGINERR_LOG_PATH_NOT_EXIT
|
|
self.logDir = logPath
|
|
if self.pluginName != None:
|
|
self.logPath = os.path.join(self.logDir, self.pluginName + ".log")
|
|
self.cmd = "bash " + self.execCmd + " fifoname=" + self.fifoName + " logpath=" + self.logPath + " loglevel=" + str(self.logLevel) + " modename=" + self.pluginName
|
|
|
|
def plg_run(self):
|
|
if not self.enabled:
|
|
logging.error("[PLUGIN]: [%s] not enabled.", self.pluginName)
|
|
return self.PLUGINERR_PLUGIN_NOT_ENABLED
|
|
|
|
self.running = True
|
|
tmptimer = threading.Timer(0.5, function=self._update_process)
|
|
tmptimer.start()
|
|
|
|
if self.cmd == None:
|
|
logging.error("[PLUGIN]: cmd is None.")
|
|
return self.PLUGINERR_CMD_IS_NONE, self._numToInfo(self.PLUGINERR_CMD_IS_NONE), self._numToInfo(self.PLUGINERR_CMD_IS_NONE)
|
|
|
|
logging.debug("[PLUGIN]: cmd[%s].",self.cmd)
|
|
try:
|
|
ret = subprocess.run(self.cmd, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
|
except Exception as e:
|
|
logging.error("[PLUGIN]: subprocess run err[%s].", e)
|
|
|
|
self.running = False
|
|
logging.debug("[PLUGIN]: [%s] run finished ret[%d].",self.pluginName, ret.returncode)
|
|
return ret.returncode, ret.stdout.decode(), ret.stderr.decode()
|
|
|
|
def plg_reconfig(self):
|
|
pass
|
|
|
|
|
|
|
|
class pluginManagerClass(pluginState):
|
|
def __init__(self):
|
|
# 变量初始化
|
|
self.plgClassList = [] # 插件句柄
|
|
self.loaded = False
|
|
|
|
self.managerLogDir = LOG_DIR_ROOT # 管理器日志路径
|
|
# 日志配置初始化,试用updater的logger
|
|
# if not os.path.exists(self.managerLogDir):
|
|
# os.mkdir(self.managerLogDir, mode=0o755)
|
|
# logfile = os.path.join(self.managerLogDir, 'PluginManager.log.' + str(self.classNum))
|
|
# logging.basicConfig(format=FORMAT, level='DEBUG', datefmt='%m-%d,%H:%M:%S', filename=logfile, filemode='a')
|
|
# self.pluginLogDir = PLUGIN_LOG_DIR # 插件日志路径
|
|
|
|
# 将单个插件句柄添加到全局记录, 并使能
|
|
def _add_single_plugin(self, filePath, enable):
|
|
if not os.path.exists(filePath):
|
|
logging.debug("[PLUGIN]: [%s] not exist.", filePath)
|
|
return
|
|
|
|
singlePlgClass = pluginClass()
|
|
singlePlgClass.plg_config(filePath)
|
|
self.plgClassList.append(singlePlgClass)
|
|
if enable:
|
|
singlePlgClass.plg_enable()
|
|
singlePlgClass.plg_set_logDir(self.pluginLogDir)
|
|
|
|
def _remove_single_plugin(self, pluginClass):
|
|
if pluginClass in self.plgClassList:
|
|
logging.debug("[PLUGIN]: remove [%s].", pluginClass.plg_get_name())
|
|
pluginClass.remove(pluginClass)
|
|
pluginClass.plg_disable()
|
|
|
|
|
|
# 加载所有插件,读取所有配置
|
|
# 第一个执行
|
|
# 返回插件句柄列表
|
|
# TODO:加载指定插件, 读取指定配置
|
|
def reload_plugin(self, pluginName):
|
|
pass
|
|
|
|
# 通过句柄获取插件名称
|
|
def get_plugin_name(self, pluginClass):
|
|
if not self.loaded:
|
|
logging.error("[PLUGIN]: please run load_all first.")
|
|
return self.PLUGINERR_NOT_LOAD_ALL
|
|
if pluginClass not in self.plgClassList:
|
|
logging.error("[PLUGIN]: there is no this plugin in pluginList.")
|
|
return self.PLUGINERR_PLUGIN_NOT_IN_LIST
|
|
|
|
return pluginClass.plg_get_name()
|
|
|
|
# 运行指定插件
|
|
# pluginName, pluginClass 都指定时,以名称为准
|
|
def run_plugin(self, pluginName = None):
|
|
self.running = True
|
|
|
|
if pluginName == None or not os.path.isfile(pluginName):
|
|
logging.error("[PLUGIN]: [%s] Cann't found.",pluginName)
|
|
return True
|
|
cmd = "bash " + pluginName
|
|
try:
|
|
ret = subprocess.run(cmd, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
|
logging.info("[PLUGIN]: script[%s].",pluginName)
|
|
except Exception as e:
|
|
logging.error("[PLUGIN]: subprocess run err[%s].", e)
|
|
return True
|
|
|
|
self.running = False
|
|
|
|
if ret.returncode != 0:
|
|
logging.error("[PLUGIN]: code:%d, out:%s, err:%s",ret.returncode, ret.stdout.decode(), ret.stderr.decode())
|
|
logging.debug("[PLUGIN]: run finished returncode[%d], out[%s], err[%s]",ret.returncode, ret.stdout.decode(), ret.stderr.decode())
|
|
return (ret.returncode==0)
|
|
|
|
def connect_signal(self, plgclass, signal, handler):
|
|
if plgclass not in self.plgClassList:
|
|
logging.error("[PLUGIN]: there is no this plugin in pluginList.")
|
|
return self.PLUGINERR_PLUGIN_NOT_IN_LIST
|
|
return plgclass.connect(signal, handler)
|
|
|
|
|
|
def plugin_process_handler(obj, process):
|
|
logging.info("[PLUGIN]: ******* process [%d].", process)
|
|
|
|
# if __name__ == "__main__":
|
|
|
|
# pMClass = pluginManagerClass()
|
|
# plgList = pMClass.load_all("./")
|
|
|
|
# for everyPlg in iter(plgList):
|
|
# name = pMClass.get_plugin_name(everyPlg)
|
|
# print("name:", name)
|
|
# desc = pMClass.get_desc(everyPlg)
|
|
# print("desc:", desc)
|
|
# pMClass.connect_signal(everyPlg, "processChanged", plugin_process_handler)
|
|
|
|
# ret = pMClass.run_plugin(name)
|
|
|
|
# exit(0)
|
|
|