• Django【设计】可插拔的插件方式实现



    需求
    在CMDB系统中,我们需要对资产进行采集和资产入库,包括serverBasic、disk、memory、nic信息等,客户端需要采集这些硬件的信息,服务端则负责资产入库,但是需要采集的硬件并不是固定不变的,我们需要根据实际情况适当的添加或者减少硬件信息的采集,所以在生产环境中,我们把每个硬件信息的采集和入库做成插件,相互独立,可在配置文件中增减或者移除。
     
    知识点:
    字符串的形式导入插件方式:(字符串的格式也就是插件这个类的路径,这种格式正好也是类的导入格式)
    # 根据字符串形式导入模块,并且找到其中的类并执行
    import importlib
    
    v = "src.plugins.nic.Nic"
    module_path,cls_name = v.rsplit('.',maxsplit=1)
    m = importlib.import_module(module_path)
    cls = getattr(m,cls_name)
    obj = cls()
    obj.process()
     
     
    设计: (参考django中间件的加载方式)
    在settings配置文件中做好插件配置,在加载插件的方法中,使用字符串形式导入类的方法,循环加载各个插件。
    settings.py
     
    plugins/__init__.py下的加载插件方法:
    def exec_plugin(self):
            server_info = {}
            for k,v in self.plugin_items.items():
                # 找到v字符串:src.plugins.nic.Nic,
                # src.plugins.disk.Disk
                info = {'status':True,'data': None,'msg':None}
                try:
                    module_path,cls_name = v.rsplit('.',maxsplit=1)
                    module = importlib.import_module(module_path)
                    cls = getattr(module,cls_name)
    
                    if hasattr(cls,'initial'):
                        obj = cls.initial()
                    else:
                        obj = cls()
                    ret = obj.process(self.exec_cmd,self.test)
                    info['data'] = ret
                except Exception as e:
                    info['status'] = False
                    info['msg'] = traceback.format_exc()
    
                server_info[k] = info
            return server_info
     
    应用实例(及整个业务逻辑):
    客户端
    执行流程:run.py >> script.py >> client.py >> plugins
    run.py,设置全局变量,主函数启动
    import sys
    import os
    
    BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASEDIR)
    
    # 设置全局变量(手动配置路径)
    os.environ['AUTO_CLIENT_SETTINGS'] = "conf.settings"
    
    # # 导入插件管理类
    # from src.plugins import PluginManager
    
    from src import script
    
    if __name__ == '__main__':
        script.start()
    View Code
     
    script.py,约束采集信息模式(agent,ssh,salt),兼容三种模式
    from lib.config import settings
    from .client import AgentClient
    from .client import SaltSshClient
    
    def start():
        # 这个函数用来判断模式,并约束可选模式
        if settings.MODE == 'AGENT':
            obj = AgentClient()
        elif settings.MODE == "SSH" or settings.MODE == 'SALT':
            obj = SaltSshClient()
        else:
            raise Exception('模式仅支持:AGENT/SSH/SALT')
        obj.exec()
    View Code
     
    client.py,三种模式的调用插件,API验证
    import requests,json,time,hashlib
    from src.plugins import PluginManager
    from lib.config import settings
    from concurrent.futures import ThreadPoolExecutor
    
    class BaseClient(object):
        def __init__(self):
            self.api = settings.API
    
        def post_server_info(self,server_dict):
            
            # requests.post(self.api,data=server_dict) # 1. k=v&k=v,   2.  content-type:   application/x-www-form-urlencoded
            response = requests.post(self.api,json=server_dict,headers={'auth-api':auth_header_val}) # 1. 字典序列化;2. 带请求头 content-type:   application/json
    
        def exec(self):
            raise NotImplementedError('必须实现exec方法')
    
    
    class AgentClient(BaseClient):  # 继承BaseClient类
    
        def exec(self):
            obj = PluginManager()
            server_dict = obj.exec_plugin()
            print('采集到的服务器信息:',server_dict)
            self.post_server_info(server_dict)
    
    
    class SaltSshClient(BaseClient):    # 继承BaseClient类
        def task(self,host):
            obj = PluginManager(host)
            server_dict = obj.exec_plugin()
            self.post_server_info(server_dict)
    
        def get_host_list(self):
            response = requests.get(self.api,headers={'auth-api':auth_header_val})
            print(response.text)    # [{"hostname":"c1.com"}]
            return json.loads(response.text)
            # return ['c1.com',]
    
        def exec(self):
            pool = ThreadPoolExecutor(10)
    
            host_list = self.get_host_list()
            for host in host_list:
                # 注意格式,如果host_list为字典,用host.hostname取值
                pool.submit(self.task,host)
    View Code
     
    plugins
      
    __init__.py,初始化插件
    import importlib
    import requests
    from lib.config import settings
    import traceback
    
    class PluginManager(object):
        def __init__(self,hostname=None):
            self.hostname = hostname
            self.plugin_items = settings.PLUGIN_ITEMS
            self.mode = settings.MODE
            self.test = settings.TEST
            if self.mode == "SSH":
                self.ssh_user = settings.SSH_USER
                self.ssh_port = settings.SSH_PORT
                self.ssh_pwd = settings.SSH_PWD
    
        def exec_plugin(self):
            server_info = {}
            for k,v in self.plugin_items.items():
                # 找到v字符串:src.plugins.nic.Nic,
                # src.plugins.disk.Disk
                info = {'status':True,'data': None,'msg':None}
                try:
                    module_path,cls_name = v.rsplit('.',maxsplit=1)
                    module = importlib.import_module(module_path)
                    cls = getattr(module,cls_name)
    
                    if hasattr(cls,'initial'):
                        obj = cls.initial()
                    else:
                        obj = cls()
                    ret = obj.process(self.exec_cmd,self.test)
                    info['data'] = ret
                except Exception as e:
                    info['status'] = False
                    info['msg'] = traceback.format_exc()
    
                server_info[k] = info
            return server_info
    
        def exec_cmd(self,cmd):
            # 调用插件方法时,会把执行命令的方法作为参数传到插件,line:48
            if self.mode == "AGENT":
                import subprocess
                result = subprocess.getoutput(cmd)
            elif self.mode == "SSH":
                import paramiko
                ssh = paramiko.SSHClient()
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                ssh.connect(hostname=self.hostname, port=self.ssh_port, username=self.ssh_user, password=self.ssh_pwd)
                stdin, stdout, stderr = ssh.exec_command(cmd)
                result = stdout.read()
                ssh.close()
            elif self.mode == "SALT":
                import subprocess
                result = subprocess.getoutput('salt "%s" cmd.run "%s"' %(self.hostname,cmd))
            else:
                raise Exception("模式选择错误:AGENT,SSH,SALT")
            return result
    View Code
     

     
    服务端
    执行流程:views.py >> plugins
    settings.py
    PLUGIN_ITEMS = {
        "nic": "api.plugins.nic.Nic",
        "disk": "api.plugins.disk.Disk",
        "memory": "api.plugins.memory.Memory",
    }
     
     
    views.py:获取未采集列表,调用写入数据库插件
            manager = PluginManager()
            response = manager.exec(server_dict)
    
            return HttpResponse(json.dumps(response))
     
     
    plugins
      
    __init__.py,初始化插件
            for k,v in self.plugin_items.items():
                print(k,v)
                # try:
                module_path,cls_name = v.rsplit('.',maxsplit=1)
                md = importlib.import_module(module_path)
                cls = getattr(md,cls_name)
                obj = cls(server_obj,server_dict[k])
                obj.process()
                print(k,v)
     
  • 相关阅读:
    监控 SQL Server 的运行状况
    软件开发报价的计算方法
    PHP 正则表达式
    ObjectiveC编程语言简介
    iframe 自适应高度,无限级父框架
    分享iphone开发的好网站,希望大家也能提供一些分享下
    实战WebService I: XMLPRC篇(基于php)
    PHPPRC
    Objective C cocos2D场景切换方式总汇
    NoSQL数据存储引擎
  • 原文地址:https://www.cnblogs.com/lucaq/p/7638543.html
Copyright © 2020-2023  润新知