zoukankan      html  css  js  c++  java
  • cmdb项目-1

    1.什么是cmdb

      配置管理数据库 ,存储基础设备的各种信息配置等

      CMDB可以存储并自动发现整个IT网络上的各种信息,比如一个IT网络上有多少台服务器、多少存储、设备的品牌、资产编号、维护人员、所属部门、服务器上运营什么操作系统、操作系统的版本、操作系统上有哪些应用、每个应用的版本等等,不仅如此,CMDB还有一个非常重要的功能——存储不同资源之间的依赖关系,如果网络上某个节点出现问题(比如某个服务器down了),通过CMDB,可以判断因此受到影响的业务

      CMDB由三个部分实现 : api系统(django)   +  资产采集系统   +  后台管理系统

    2.知识点分解

      1)django实现api ,python普通项目实现client ,完成二者间的通信

        api使用Django提供的rest_framwork模块

          APIView避免csrf对post的限制

          request.data中存放了客户端post请求的数据

          Response方法将数据进行json转换

        client使用requests和json模块

          requests.get对api发起get请求获取到的数据可以使用content拿出Byte类型

          requests.post对api发起post提交数据必须提交json编码后的类型 

    ###api
    ##路由
    urlpatterns = [
        url(r'^asset/', views.Asset.as_view()),
    ]
    ##视图函数
    from django.shortcuts import render, HttpResponse
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    class Asset(APIView):
        def get(self, request):
            server_info = ['master1', 'master2']
            return Response(server_info)
    
        def post(self, request):
            print(request.data)
            return HttpResponse('200ok')
    ###client端
    import
    requests import json client_info = {'hostname': 'c1.com'} r1 = requests.get( url='http://127.0.0.1:8000/api/asset/' ) r1_data = json.loads(r1.content.decode('utf-8')) print(r1_data)    r2 = requests.post(                      ###post提交数据一定要加请求头部标记json格式 url='http://127.0.0.1:8000/api/asset/', data=json.dumps(client_info).encode('utf-8'), headers={ 'content-type': 'application/json' } )

       2)通过字符串加载文件中的类 ,实现开放封闭必备小点

        我们如何在配置中定义使用哪个文件中的类呢 ,使用字典 ,以点分割 目录.文件.类名

        如 : agent模式对应了一个字符串它对应了 /src/engine/agent.py文件中的AgentHandler类

    ###settings.py
    ENGINE = 'agent' ENGINE_DICT = { 'agent': 'src.engine.agent.AgentHandler', 'ssh': 'src.engine.ssh.SSHHandler', }

    ####设计一个函数完成从文件中获取类
    import importlib
    def import_string(class_string):
    """
    根据配置中字符串获取文件中的类
    :param 'src.engine.agent.AgentHandler',
    module, engine = src.engine.agent 和 AgentHandler
    importlib.import_module()就是import src.engine.agent
    engine_class = 反射获取到AgentHandler类
    """
    module, engine = class_string.rsplit('.', maxsplit=1)
    module_file = importlib.import_module(module)
    engine_class = getattr(module_file, engine)
    return engine_class
    
    

    3.CMDB资产采集系统简单实现要点

      采集模式engine
        agent模式 ,每台主机安装client ,执行命令将采集的数据上报api
        中控机模式(ssh ansible) ,使用一台机器远程所有的主机 ,执行命令完成资产采集 ,再将数据上报api
        调用issa层的api接口直接获取基础设备信息

      程序设计思想

        开放封闭原则 ,代码封闭 ,配置开放(定义当前的使用的engine与plugin),支持扩展

      程序设计关注点

        资产采集

          engine采集模式可扩展 (抽象接口)

          plugin命令插件可扩展 (抽象接口)

        唯一标识
        错误处理
        日志

    4.CMDB资产采集系统简单实现代码

      1)API端后续完善 ,先使用标题2-1的api

      2)新建项目 ,完善常规目录

      3)程序入口/bin/client

        执行run()

    import os
    import sys
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    
    from src.script import run
    
    if __name__ == '__main__':
        run()

      4)资产采集上报入口/src/script

        定义run()

        根据配置settings实例化engine的对象 ,并执行对象的handler方法

    from conf import settings
    from lib.import_class import import_string    #标题2-2的根据配置字符串取类
    
    def run():
        """资产采集入口"""
        engine_path = settings.ENGINE_DICT.get(settings.ENGINE)
        engine_class = import_string(engine_path)                     
        obj = engine_class()
        obj.handler()

      5)采集模式engine实现handler()方法

        定义基类约束BaseHandler() 要求每个engine必须有handler()完成采集与上报, cmd()完成调用命令窗口

        定义基类简化ssh这一类的插件SSHandSaltHandler(BaseHandler)  ,这一类的handler()方法都是从api获取主机列表 ,远程采集设备信息 ,再提交api ,所以会出现同时对很多的主机进行连接 ,这里使用线程池

    import requests
    from concurrent.futures import ThreadPoolExecutor
    from ..plugins import get_server_info
    import json
    
    
    class BaseHandler():
        """
        定义engine的基类 ,每个engine都要有这两个方法
        """
    
        def handler(self):
            raise NotImplementedError('handler() must be Implemented')
    
        def cmd(self, command, hostname=None):
            raise NotImplementedError('cmd() must be Implemented')
    
    
    class SShandSaltHandler(BaseHandler):
        """
        简化ssh这一类engine的代码
        """
    
        def handler(self):
            # 1.获取主机列表
            r1 = requests.get(
                url='http://127.0.0.1:8000/api/asset/',
            )
            host_list = r1.json()
    
            # 2.创建线程池
            pool = ThreadPoolExecutor(20)
    
            # 3.提交任务给线程池
            for hostname in host_list:
                pool.submit(self.task, hostname)
    
        def task(self, hostname):
            """线程池任务"""
            info = get_server_info(self, hostname)
            r1 = requests.post(
                url='http://127.0.0.1:8000/api/asset/',
                data=json.dumps(info).encode('gbk'),
                headers={
                    'content-type': 'application/json'
                }
            )

        engine--agent模式实现handler()

          cmd()方法使用subprocess模块完成本地命令调用

    from src.engine.base import BaseHandler
    from ..plugins import get_server_info
    import requests
    import json
    
    
    class AgentHandler(BaseHandler):
        """定义cmd窗口 ,操控资产采集 + 上报"""
    
        def cmd(self, command, hostname=None):
            import subprocess
            return subprocess.getoutput(command)
    
        def handler(self):
            info = get_server_info(self)
            r1 = requests.post(
                url='http://127.0.0.1:8000/api/asset/',
                data=json.dumps(info).encode('gbk'),
                headers={
                    'content-type': 'application/json'
                }
            )
    View Code

        engine--ssh模式实现handler()

          cmd()方法使用paramiko模块完成远程命令调用 (秘钥需要配置在settings中)

    from src.engine.base import SShandSaltHandler
    from conf import settings
    
    
    class SSHandler(SShandSaltHandler):
        """仅定义cmd即可 ,采集与上报在父类中"""
    
        def cmd(self, command, hostname=None):
            import paramiko
            private_key = paramiko.RSAKey.from_private_key_file(settings.SSH_PRIVATE_KEY)
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostname=hostname, port=settings.SSH_PORT, username=settings.SSH_USER, pkey=private_key)
            stdin, stdout, stderr = ssh.exec_command(command)
            result = stdout.read()
            ssh.close()
            return result
    View Code

      6)handler方法中的数据采集使用了get_server_info()方法

        script.py与__init__有异曲同工之处 ! 依据配置应用不同插件

    from conf import settings
    from lib.import_class import import_string
    
    def get_server_info(handler, hostname=None):
        """采集信息入口"""
        info = {}
        for name, path in settings.PLUGINS_DICT.items():
            """
            {'disk':'src.plugins.disk.Disk'}
            """
            plugin_class = import_string(path)
            obj = plugin_class()
            info = obj.process(handler, hostname)
        return info

      7)命令插件plugins实现process()方法

        定义基类约束BasePlugin

          增加debug属性判断是否为调试模式 ,增加项目根路径

          每个命令插件都要分为window与linux的具体系统判断执行什么命令

    from conf import settings
    
    class BasePlugin:
        def __init__(self):
            self.debug = settings.DEBUG
            self.base_dir = settings.BASE_DIR
    
        def get_os(self, handler, hostname=None):  ## 调试
            # os = handler.cmd('命令',hostname)
            return 'linux'
    
        def win(self, handler, hostname=None):
            raise NotImplementedError('win() must be Implemented')
    
        def linux(self, handler, hostname=None):
            raise NotImplementedError('linux() must be Implemented')
    
        def process(self, handler, hostname=None):
            os = self.get_os(handler, hostname)
            if os == 'win':
                ret = self.win(handler, hostname)
            else:
                ret = self.linux(handler, hostname)
    
            return ret

        查看硬盘的命令插件

          其中process--->调用win或者linux--->执行handler对象的cmd()方法 (这个对象最开始就被当做参数传过来了)

          其中还会有debug判断 ,如果是调试就直接从文件获取数据了

          其中parse方法是将采集的数据格式化 ,需要根据api所需要的格式进行格式化

    from .base import BasePlugin
    import os
    import re
    
    
    class Disk(BasePlugin):
        def win(self, handler, hostname=None):
            ret = handler.cmd('dir', hostname)[:60]
    
            return 'Disk'
    
        def linux(self, handler, hostname=None):
            if self.debug:
                with open(os.path.join(self.base_dir, 'files', 'disk.out')) as f:
                    ret = f.read()
            else:
                ret = handler.cmd('ifconfig', hostname)[:60]
            return self.parse(ret)
    
        def parse(self, content):
            """
            解析shell命令返回结果
            :param content: shell 命令结果
            :return:解析后的结果
            """
            response = {}
            result = []
            for row_line in content.split("
    
    
    
    "):
                result.append(row_line)
            for item in result:
                temp_dict = {}
                for row in item.split('
    '):
                    if not row.strip():
                        continue
                    if len(row.split(':')) != 2:
                        continue
                    key, value = row.split(':')
                    name = self.mega_patter_match(key)
                    if name:
                        if key == 'Raw Size':
                            raw_size = re.search('(d+.d+)', value.strip())
                            if raw_size:
                                temp_dict[name] = raw_size.group()
                            else:
                                raw_size = '0'
                        else:
                            temp_dict[name] = value.strip()
                if temp_dict:
                    response[temp_dict['slot']] = temp_dict
            return response
    
        @staticmethod
        def mega_patter_match(needle):
            grep_pattern = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'}
            for key, value in grep_pattern.items():
                if needle.startswith(key):
                    return value
            return False
    View Code

        查看内存的命令插件

    from .base import BasePlugin
    import os
    from lib import convert
    
    
    class Memory(BasePlugin):
        def win(self, handler, hostname=None):
            """
            windowns下执行命令
            :param handler:
            :param hostname:
            :return:
            """
            ret = handler.cmd('dir', hostname)[:60]
    
            return 'Disk'
    
        def linux(self, handler, hostname=None):
            if self.debug:
                with open(os.path.join(self.base_dir, 'files', 'memory.out')) as f:
                    ret = f.read()
            else:
                ret = handler.cmd('lsblk', hostname)[:60]
    
            return ret
    
        def parse(self, content):
            """
            解析shell命令返回结果
            :param content: shell 命令结果
            :return:解析后的结果
            """
            ram_dict = {}
            key_map = {
                'Size': 'capacity',
                'Locator': 'slot',
                'Type': 'model',
                'Speed': 'speed',
                'Manufacturer': 'manufacturer',
                'Serial Number': 'sn',
    
            }
            devices = content.split('Memory Device')
            for item in devices:
                item = item.strip()
                if not item:
                    continue
                if item.startswith('#'):
                    continue
                segment = {}
                lines = item.split('
    	')
                for line in lines:
                    if len(line.split(':')) > 1:
                        key, value = line.split(':')
                    else:
                        key = line.split(':')[0]
                        value = ""
                    if key in key_map:
                        if key == 'Size':
                            segment[key_map['Size']] = convert.convert_mb_to_gb(value, 0)
                        else:
                            segment[key_map[key.strip()]] = value.strip()
                ram_dict[segment['slot']] = segment
    
            return ram_dict
    View Code

    5.简单一些想法完成开放封闭

      首先属于同类功能 ,这类的功能是需要不停增加的 ,或者说可以选用的都通过配置实现 ,使用抽象类约束

        配置 :指定操作者  ,操作者使用的工具

        生成操作者(script)

        handler操作者: 可以操作任意工具

        生成操作者使用的工具(__init__)  

        plugin工具: 网卡查看工具  硬盘查看工具查看

  • 相关阅读:
    亚马逊云服务器VPS Amazon EC2 免费VPS主机配置CentOS及其它内容
    Linux + Mono 目前已经支持Entity Framework 6.1
    CentOS上 Mono 3.2.8运行ASP.NET MVC4经验
    Linux CentOS下如何确认MySQL服务已经启动
    C#使用Timer.Interval指定时间间隔与指定时间执行事件
    MySQL数据库有外键约束时使用truncate命令的办法
    C++中字符和字符串的读取与使用
    结构体的运算符重载
    P1358 扑克牌
    P1284 三角形牧场
  • 原文地址:https://www.cnblogs.com/quguanwen/p/11498534.html
Copyright © 2011-2022 走看看