#!/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)