• CMDB项目


    一 CMDB简介

    1.1 什么是CMDB?

    CMDB(资产管理系统)是所有运维工具的数据基础

    1.2 CMDB包含的功能

    用户管理,记录测试,开发,运维人员的用户表

    业务线管理,需要记录业务的详情

    项目管理,指定此项目用属于哪条业务线,以及项目详情

    应用管理,指定此应用的开发人员,属于哪个项目,和代码地址,部署目录,部署集群,依赖的应用,软件等信息

    主机管理,包括云主机,物理机,主机属于哪个集群,运行着哪些软件,主机管理员,连接哪些网络设备,云主机的资源池,存储等相关信息

    主机变更管理,主机的一些信息变更,例如管理员,所属集群等信息更改,连接的网络变更等

    网络设备管理,主要记录网络设备的详细信息,及网络设备连接的上级设备

    IP管理,IP属于哪个主机,哪个网段, 是否被占用等

    1.3 实现的四种方式

    1.3.1 Agent实现方式

    Agent方式,可以将服务器上面的Agent程序作定时任务,定时将资产信息提交到指定API录入数据库

    image-20210312154829211

    其本质上就是在各个服务器上执行subprocess.getoutput()命令,然后将每台机器上执行的结果,返回给主机API,然后主机API收到这些数据之后,放入到数据库中,最终通过web界面展现给用户

    #linux
    import subprocess
    import re
    res = subprocess.getoutput("ifconfig")
    print(res)
    ip=re.findall('inet (.*?) netmask',res)
    print(ip)
    
    # windows
    import subprocess
    import re
    res=subprocess.getoutput('ipconfig')
    
    print(res)
    ip=re.findall('IPv4 地址 . . . . . . . . . . . . : (.*)',res)
    print(ip)
    
    

    优点:速度快
    缺点:需要为每台服务器部署一个Agent程序

    使用crontab定时执行python脚本

    # 1 进入创建crontab定时任务
    crontab -e 
    # 2 写入任务(每分钟执行一次test.py)
    * * * * * python3 test.py
    # 3 编写test.py
    with open('a.txt','a') as f:
        f.write('hello world')
        
    # 4 查看定时任务
    crontab -l 
    

    1.3.2 ssh实现方式 (基于Paramiko模块)

    中控机通过Paramiko(py模块)登录到各个服务器上,然后执行命令的方式去获取各个服务器上的信息

    image-20210312155000231

    优点:无Agent

    缺点:速度慢

    如果在服务器较少的情况下,可应用此方法

    import paramiko
    import re
    #创建SSH对象
    ssh = paramiko.SSHClient()
    
    # 允许连接不在know_hosts文件中的主机
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy)
    
    # 连接服务器
    ssh.connect(hostname='101.133.225.166',port=22,username='root',password='')
    # 执行命令
    stdin,stdout,stderr = ssh.exec_command('ifconfig')
    
    # 获取命令结果
    result = stdout.read().decode('utf-8')
    
    print(result)
    ip=re.findall('inet (.*?) netmask',result)
    print(ip)
    # 关闭连接
    ssh.close()
    

    1.3.3 saltstack方式

    image-20210312155144213

    此方案本质上和第二种方案大致是差不多的流程,中控机发送命令给服务器执行。服务器将结果放入另一个队列中,中控机获取将服务信息发送到API进而录入数据库。

    执行流程:
    第一步: 由管理员录入资产(主机名,SN等信息),通过后台管理,录入数据库
    第二步: salt-master从数据库获取未采集资产信息的服务器
    第三步: salt-master发送命令给salt-minion执行
    第四步: salt-master拿到执行结果
    第五步: 将结果发送给API
    第六步: API将其写入数据库

    解释:
    salt-master可以理解为主人
    salt-minion可以理解为奴隶

    优点:快,开发成本低

    缺点:依赖于第三方工具

    salstack的安装和配置

    1.安装和配置

    master端:
    """
    1. 安装salt-master
        yum install salt-master
    2. 修改配置文件:/etc/salt/master
        interface: 0.0.0.0    # 表示Master的IP 
    3. 启动
        service salt-master start
    """
    slave端:
    """
    1. 安装salt-minion
        yum install salt-minion
    2. 修改配置文件 /etc/salt/minion
        master: 10.211.55.4           # master的地址
        或
        master:
            - 10.211.55.4
            - 10.211.55.5
        random_master: True
        id: c2.salt.com                    # 客户端在salt-master中显示的唯一ID
    3. 启动
        service salt-minion start
    

    2.授权

    salt-key -L                    # 查看已授权和未授权的slave
    salt-key -a  salve_id      # 接受指定id的salve
    salt-key -r  salve_id      # 拒绝指定id的salve
    salt-key -d  salve_id      # 删除指定id的salve
    

    3.执行命令

    在master服务器上对salve进行远程操作

    salt 'c2.salt.com' cmd.run  'ifconfig'
    # 基于API的方式
    
    import salt.client
    local = salt.client.LocalClient()
    result = local.cmd('c2.salt.com', 'cmd.run', ['ifconfig'])
    

    1.3.4 Puppet(ruby语言开发)(了解)

    每隔30分钟,通过RPC消息队列将执行的结果返回给用户

    二 三种方案客户端编写

    2.1 目录结构划分

    autoclient               # 项目名
      -bin                   # 启动文件路径
      	-start.py            # 启动文件
    	-config                # 配置文件路径
        -cert                # 私钥
        -custom_settings.py  # 用户自定义配置
    	-files                 # 测试数据文件
        -board.out
        -cpuinfo.out
        -disk.out
        -memory.out
        -nic.out
      -lib                  # 库文件夹
        -conf               # 配置信息文件夹
        	-config.py        # 配置类
        	-global_settings.py # 全局常量配置
        -convert.py          # 公共方法
      -src                  # 源文件
        -plugins             # 插件
          -__init__.py       # 初始化文件
          -basic.py          
          -board.py
          -cpu.py
          -disk.py
          -memory.py
          -nic.py
        script.py           # 脚本文件
        client.py           # 客户端类
    tests                   # 测试文件夹
    
    
    # 总结:bin,config,files,lib,src几个文件夹
    

    2.2 仿django配置文件

    custom_settings.py

    # 用户配置
    PORT = 22
    USER = 'lqz'
    

    global_settings.py

    #### 全局配置
    
    PORT = 22
    USER = 'root'
    

    config.py

    from config import custom_settings
    from . import global_settings
    
    class Settings():
        def __init__(self):
    
            #### 全局配置
            for key in dir(global_settings):
                if key.isupper():
                    #### 获取key所对应的值
                    v = getattr(global_settings, key)
                    #### 设置key以及值到当前的setting对象
                    setattr(self, key, v)
    
            #### 自定制配置
            for key in dir(custom_settings):
                if key.isupper():
                     #### 获取key所对应的值
                     v = getattr(custom_settings, key)
                     #### 设置key以及值到当前的setting对象
                     setattr(self, key, v)
    
    settings = Settings()
    

    2.3 可插拔式配置

    custom_settings.py

    ### 可插拔式的采集,注释掉某个就不会执行
    PLUGINS_DICT = {
        'basic':'src.plugins.basic.Basic',
        'board':'src.plugins.board.Board',
        'cpu':'src.plugins.cpu.Cpu',
        'disk':'src.plugins.disk.Disk',
        'nic':'src.plugins.nic.Nic',
        'memory':'src.plugins.memory.Memory',
    }
    

    src/plugins/__init__.py

    import traceback
    
    from lib.conf.config import settings
    import importlib
    import subprocess
    ### 管理插件信息的类
    class PluginsManager(object):
    
        def __init__(self, hostname=None):
            pass
    
        ### 读取配置文件中的pluginsdict, 并执行对应模块中的process方法
        def execute(self):
            response = {}
            for k, v in self.plugins_dict.items():
                ret = {"status":None, 'data':None}
                '''
                k:  board,...
                v:  src.plugins.board.Board   字符串
                '''
                try:
                    # 1. 导入模块路径
                    moudle_path, class_name = v.rsplit('.', 1)
                    # 2. 导入这个路径
                    moudle_name = importlib.import_module(moudle_path)
                    # 3. 导入对应模块下的类
                    classobj = getattr(moudle_name, class_name)
                    # 4. 执行类下面对应的process方法
                    res = classobj().process()
                except Exception as e:
    								pass
            return response
    

    src/plugins/cpu.py

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import os
    from lib.conf.config import settings
    
    class Cpu(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            print('cpu print')
    

    src/plugins/disk.py

    import os
    from lib.conf.config import settings
    
    class Cpu(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            print('disk print')
    

    2.4 冗余代码抽取

    继承方式

    把函数当参数传入函数中:

    在src/plugins/init.py中写,__隐藏,调用execute的时候,把函数地址和命令传入

    import traceback
    
    from lib.conf.config import settings
    import importlib
    import subprocess
    ### 管理插件信息的类
    class PluginsManager(object):
    
        def __init__(self, hostname=None):
            self.plugins_dict = settings.PLUGINS_DICT
            self.hostname = hostname  # 采集客户端的地址
            self.debug = settings.DEBUG
            if settings.MODE == 'ssh': # ssh方式才需要端口,用户名,密码,这些应该放到配置文件中
                self.port = settings.SSH_PORT
                self.name = settings.SSH_USERNAME
                self.pwd  = settings.SSH_PASSWORD
    
        ### 读取配置文件中的pluginsdict, 并执行对应模块中的process方法
        def execute(self):
            response = {}
            for k, v in self.plugins_dict.items():
                ret = {"status":None, 'data':None}
                '''
                k:  board,...
                v:  src.plugins.board.Board   字符串
                '''
                try:
                    # 1. 导入模块路径
                    moudle_path, class_name = v.rsplit('.', 1)
                    # 2. 导入这个路径
                    moudle_name = importlib.import_module(moudle_path)
                    # 3. 导入对应模块下的类
                    classobj = getattr(moudle_name, class_name)
                    # 4. 执行类下面对应的process方法
                    res = classobj().process(self.__cmd_run, self.debug)
                    ret['status'] = 10000
                    ret['data'] = res
                except Exception as e:
                    ret['status'] = 10001
                    ret['data']=  "[%s] 采集 [%s] 出错了, 错误信息是:%s" % (self.hostname if self.hostname else "Agent", k, str(traceback.format_exc()))
                response[k] = ret
            return response
    
    
        def __cmd_run(self, cmd):
            if settings.MODE == 'agent':
                return self.__cmd_agent(cmd)
            elif settings.MODE == 'ssh':
                return self.__cmd_ssh(cmd)
            elif settings.MODE == 'salt':
                return self.__cmd_salt(cmd)
            else:
                print("只支持的模式有:agent/ssh/salt")
    
        def __cmd_agent(self, cmd):
            res = subprocess.getoutput(cmd)
            return res
    
        def __cmd_ssh(self, cmd):
            import paramiko
            # 创建SSH对象
            ssh = paramiko.SSHClient()
            # 允许连接不在know_hosts文件中的主机
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            # 连接服务器
            ssh.connect(hostname=self.hostname, port=self.port, username=self.name, password=self.pwd)
            # 执行命令
            stdin, stdout, stderr = ssh.exec_command(cmd)
            # 获取命令结果
            result = stdout.read()
            # 关闭连接
            ssh.close()
            return result
    
        def __cmd_salt(self, cmd):
            command = "salt %s cmd.run %s" % (self.hostname, cmd)
            res = subprocess.getoutput(command)
            return res
    

    在cpu.py disk.py中编写

    class Cpu(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/cpuinfo.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("cat /proc/cpuinfo")
            return self.parse(output)
    

    2.5 解析数据(以主板为例)

    # sudo dmidecode -t1 https://ipcmen.com/dmidecode
    # 可以获取BIOS,系统,主板,处理器,内存,缓存等 序列号、电脑厂商、串口信息以及其它系统配件信息
    res  = '''
    SMBIOS 2.7 present.
    
    Handle 0x0001, DMI type 1, 27 bytes
    System Information
    	Manufacturer: Parallels Software International Inc.
    	Product Name: Parallels Virtual Platform
    	Version: None
    	Serial Number: Parallels-1A 1B CB 3B 64 66 4B 13 86 B0 86 FF 7E 2B 20 30
    	UUID: 3BCB1B1A-6664-134B-86B0-86FF7E2B2030
    	Wake-up Type: Power Switch
    	SKU Number: Undefined
    	Family: Parallels VM
    '''
    
    key_map = {
        "Manufacturer" : 'manufacturer',
        "Product Name" : 'product_name',
        "Serial Number": 'sn'
    }
    
    result = {}
    data = res.strip().split('
    ')
    # print(data)
    for k in data:
        v = (k.strip().split(':'))
        if len(v) == 2:
            if v[0] in key_map:
               result[key_map[v[0]]]  = v[1].strip()
    
    print(result)
    
    '''
    result = {
        'manufacturer' : 'Parallels Software International Inc.'   ,
        'product_name' : 'Parallels Virtual Platform',
        'sn' : 'Parallels-1A 1B CB 3B 64 66 4B 13 86 B0 86 FF 7E 2B 20 30'
    }
    '''
    

    2.6 代码整合

    plugins
      -__init__.py
      -basic.py
      -board.py
      -cpu.py
      -disk.py
      -memory.py
      -nic.py
    
    #__init__.py
    import traceback
    
    from lib.conf.config import settings
    import importlib
    import subprocess
    ### 管理插件信息的类
    class PluginsManager(object):
    
        def __init__(self, hostname=None):
            self.plugins_dict = settings.PLUGINS_DICT
            self.hostname = hostname
            self.debug = settings.DEBUG
            if settings.MODE == 'ssh':
                self.port = settings.SSH_PORT
                self.name = settings.SSH_USERNAME
                self.pwd  = settings.SSH_PASSWORD
    
        ### 读取配置文件中的pluginsdict, 并执行对应模块中的process方法
        def execute(self):
            response = {}
            for k, v in self.plugins_dict.items():
                ret = {"status":None, 'data':None}
                '''
                k:  board,...
                v:  src.plugins.board.Board   字符串
                '''
                try:
                    # 1. 导入模块路径
                    moudle_path, class_name = v.rsplit('.', 1)
                    # 2. 导入这个路径
                    moudle_name = importlib.import_module(moudle_path)
                    # 3. 导入对应模块下的类
                    classobj = getattr(moudle_name, class_name)
                    # 4. 执行类下面对应的process方法
                    res = classobj().process(self.__cmd_run, self.debug)
                    ret['status'] = 10000
                    ret['data'] = res
                except Exception as e:
                    ret['status'] = 10001
                    ret['data']=  "[%s] 采集 [%s] 出错了, 错误信息是:%s" % (self.hostname if self.hostname else "Agent", k, str(traceback.format_exc()))
                response[k] = ret
            return response
    
    
        def __cmd_run(self, cmd):
            if settings.MODE == 'agent':
                return self.__cmd_agent(cmd)
            elif settings.MODE == 'ssh':
                return self.__cmd_ssh(cmd)
            elif settings.MODE == 'salt':
                return self.__cmd_salt(cmd)
            else:
                print("只支持的模式有:agent/ssh/salt")
    
        def __cmd_agent(self, cmd):
            res = subprocess.getoutput(cmd)
            return res
    
        def __cmd_ssh(self, cmd):
            import paramiko
            # 创建SSH对象
            ssh = paramiko.SSHClient()
            # 允许连接不在know_hosts文件中的主机
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            # 连接服务器
            ssh.connect(hostname=self.hostname, port=self.port, username=self.name, password=self.pwd)
            # 执行命令
            stdin, stdout, stderr = ssh.exec_command(cmd)
            # 获取命令结果
            result = stdout.read()
            # 关闭连接
            ssh.close()
            return result
    
        def __cmd_salt(self, cmd):
            command = "salt %s cmd.run %s" % (self.hostname, cmd)
            res = subprocess.getoutput(command)
            return res
    
    # basic.py
    
    class Basic(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = {
                    'os_platform': "linux",
                    'os_version': "CentOS release 6.6 (Final)
    Kernel 
     on an m",
                    'hostname': 'c2000.com'
                }
            else:
                output = {
                    'os_platform': command_func("uname").strip(),
                    'os_version': command_func("cat /etc/issue").strip().split('
    ')[0],
                    'hostname': command_func("hostname").strip(),
                }
    
    
            return output
    
    # board.py
    import os
    from lib.conf.config import settings
    
    
    class Board(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/board.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("sudo dmidecode -t1")
            return self.parse(output)
    
        def parse(self, content):
    
            result = {}
            key_map = {
                'Manufacturer': 'manufacturer',
                'Product Name': 'model',
                'Serial Number': 'sn',
            }
    
            for item in content.split('
    '):
                row_data = item.strip().split(':')
    
                if len(row_data) == 2:
                    if row_data[0] in key_map:
                        result[key_map[row_data[0]]] = row_data[1].strip() if row_data[1] else row_data[1]
    
            return result
    
    # cpu.py
    import os
    from lib.conf.config import settings
    
    class Cpu(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/cpuinfo.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("cat /proc/cpuinfo")
            return self.parse(output)
    
        def parse(self, content):
            """
            解析shell命令返回结果
            :param content: shell 命令结果
            :return:解析后的结果
            """
            response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''}
    
            cpu_physical_set = set()
    
            content = content.strip()
            for item in content.split('
    
    '):
                for row_line in item.split('
    '):
                    key, value = row_line.split(':')
                    key = key.strip()
                    if key == 'processor':
                        response['cpu_count'] += 1
                    elif key == 'physical id':
                        cpu_physical_set.add(value)
                    elif key == 'model name':
                        if not response['cpu_model']:
                            response['cpu_model'] = value
            response['cpu_physical_count'] = len(cpu_physical_set)
    
            return response
    
    #disk.py
    import re
    import os
    from lib.conf.config import settings
    
    
    class Disk(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/disk.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("sudo MegaCli  -PDList -aALL")
            return self.parse(output)
    
        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
    
    # memory.py
    import os
    from lib import convert
    from lib.conf.config import settings
    
    
    class Memory(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/memory.out'), 'r', encoding='utf-8').read()
            else:
                output = command_func("sudo dmidecode  -q -t 17 2>/dev/null")
    
            return self.parse(output)
    
        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 not line.strip():
                        continue
                    if len(line.split(':')):
                        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
    
    #nic.py 网络接口控制器
    import os
    import re
    from lib.conf.config import settings
    
    class Nic(object):
        def __init__(self):
            pass
    
        @classmethod
        def initial(cls):
            return cls()
    
        def process(self, command_func, debug):
            if debug:
                output = open(os.path.join(settings.BASEDIR, 'files/nic.out'), 'r', encoding='utf-8').read()
                interfaces_info = self._interfaces_ip(output)
            else:
                interfaces_info = self.linux_interfaces(command_func)
    
            self.standard(interfaces_info)
    
            return interfaces_info
    
        def linux_interfaces(self, command_func):
            '''
            Obtain interface information for *NIX/BSD variants
            '''
            ifaces = dict()
            ip_path = 'ip'
            if ip_path:
                cmd1 = command_func('sudo {0} link show'.format(ip_path))
                cmd2 = command_func('sudo {0} addr show'.format(ip_path))
                ifaces = self._interfaces_ip(cmd1 + '
    ' + cmd2)
            return ifaces
    
        def which(self, exe):
            def _is_executable_file_or_link(exe):
                # check for os.X_OK doesn't suffice because directory may executable
                return (os.access(exe, os.X_OK) and
                        (os.path.isfile(exe) or os.path.islink(exe)))
    
            if exe:
                if _is_executable_file_or_link(exe):
                    # executable in cwd or fullpath
                    return exe
    
                # default path based on busybox's default
                default_path = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin'
                search_path = os.environ.get('PATH', default_path)
                path_ext = os.environ.get('PATHEXT', '.EXE')
                ext_list = path_ext.split(';')
    
                search_path = search_path.split(os.pathsep)
                if True:
                    # Add any dirs in the default_path which are not in search_path. If
                    # there was no PATH variable found in os.environ, then this will be
                    # a no-op. This ensures that all dirs in the default_path are
                    # searched, which lets salt.utils.which() work well when invoked by
                    # salt-call running from cron (which, depending on platform, may
                    # have a severely limited PATH).
                    search_path.extend(
                        [
                            x for x in default_path.split(os.pathsep)
                            if x not in search_path
                        ]
                    )
                for path in search_path:
                    full_path = os.path.join(path, exe)
                    if _is_executable_file_or_link(full_path):
                        return full_path
    
            return None
    
        def _number_of_set_bits_to_ipv4_netmask(self, set_bits):  # pylint: disable=C0103
            '''
            Returns an IPv4 netmask from the integer representation of that mask.
    
            Ex. 0xffffff00 -> '255.255.255.0'
            '''
            return self.cidr_to_ipv4_netmask(self._number_of_set_bits(set_bits))
    
        def cidr_to_ipv4_netmask(self, cidr_bits):
            '''
            Returns an IPv4 netmask
            '''
            try:
                cidr_bits = int(cidr_bits)
                if not 1 <= cidr_bits <= 32:
                    return ''
            except ValueError:
                return ''
    
            netmask = ''
            for idx in range(4):
                if idx:
                    netmask += '.'
                if cidr_bits >= 8:
                    netmask += '255'
                    cidr_bits -= 8
                else:
                    netmask += '{0:d}'.format(256 - (2 ** (8 - cidr_bits)))
                    cidr_bits = 0
            return netmask
    
        def _number_of_set_bits(self, x):
            '''
            Returns the number of bits that are set in a 32bit int
            '''
            # Taken from http://stackoverflow.com/a/4912729. Many thanks!
            x -= (x >> 1) & 0x55555555
            x = ((x >> 2) & 0x33333333) + (x & 0x33333333)
            x = ((x >> 4) + x) & 0x0f0f0f0f
            x += x >> 8
            x += x >> 16
            return x & 0x0000003f
    
        def _interfaces_ip(self, out):
            '''
            Uses ip to return a dictionary of interfaces with various information about
            each (up/down state, ip address, netmask, and hwaddr)
            '''
            ret = dict()
            right_keys = ['name', 'hwaddr', 'up', 'netmask', 'ipaddrs']
    
            def parse_network(value, cols):
                '''
                Return a tuple of ip, netmask, broadcast
                based on the current set of cols
                '''
                brd = None
                if '/' in value:  # we have a CIDR in this address
                    ip, cidr = value.split('/')  # pylint: disable=C0103
                else:
                    ip = value  # pylint: disable=C0103
                    cidr = 32
    
                if type_ == 'inet':
                    mask = self.cidr_to_ipv4_netmask(int(cidr))
                    if 'brd' in cols:
                        brd = cols[cols.index('brd') + 1]
                return (ip, mask, brd)
    
            groups = re.compile('
    ?
    \d').split(out)
            for group in groups:
                iface = None
                data = dict()
    
                for line in group.splitlines():
                    if ' ' not in line:
                        continue
                    match = re.match(r'^d*:s+([w.-]+)(?:@)?([w.-]+)?:s+<(.+)>', line)
                    if match:
                        iface, parent, attrs = match.groups()
                        if 'UP' in attrs.split(','):
                            data['up'] = True
                        else:
                            data['up'] = False
                        if parent and parent in right_keys:
                            data[parent] = parent
                        continue
    
                    cols = line.split()
                    if len(cols) >= 2:
                        type_, value = tuple(cols[0:2])
    
                        iflabel = cols[-1:][0]
                        if type_ in ('inet',):
                            if 'secondary' not in cols:
                                ipaddr, netmask, broadcast = parse_network(value, cols)
                                if type_ == 'inet':
                                    if 'inet' not in data:
                                        data['inet'] = list()
                                    addr_obj = dict()
                                    addr_obj['address'] = ipaddr
                                    addr_obj['netmask'] = netmask
                                    addr_obj['broadcast'] = broadcast
                                    data['inet'].append(addr_obj)
                            else:
                                if 'secondary' not in data:
                                    data['secondary'] = list()
                                ip_, mask, brd = parse_network(value, cols)
                                data['secondary'].append({
                                    'type': type_,
                                    'address': ip_,
                                    'netmask': mask,
                                    'broadcast': brd,
                                })
                                del ip_, mask, brd
                        elif type_.startswith('link'):
                            data['hwaddr'] = value
                if iface:
                    if iface.startswith('pan') or iface.startswith('lo') or iface.startswith('v'):
                        del iface, data
                    else:
                        ret[iface] = data
                        del iface, data
            return ret
    
        def standard(self, interfaces_info):
    
            for key, value in interfaces_info.items():
                ipaddrs = set()
                netmask = set()
                if not 'inet' in value:
                    value['ipaddrs'] = ''
                    value['netmask'] = ''
                else:
                    for item in value['inet']:
                        ipaddrs.add(item['address'])
                        netmask.add(item['netmask'])
                    value['ipaddrs'] = '/'.join(ipaddrs)
                    value['netmask'] = '/'.join(netmask)
                    del value['inet']
    
    # lib/convert.py  
    def convert_to_int(value,default=0):
    
        try:
            result = int(value)
        except Exception as e:
            result = default
    
        return result
    
    def convert_mb_to_gb(value,default=0):
    
        try:
            value = value.strip('MB')
            result = int(value)
        except Exception as e:
            result = default
    
        return result
    
    # bin/start.py
    from src.plugins import PluginsManager
    if __name__ == '__main__':
        res=PluginsManager().execute()
        print(res)
    

    注意

    sudo dmidecode -t1

    可以获取BIOS,系统,主板,处理器,内存,缓存等 序列号、电脑厂商、串口信息以及其它系统配件信息

    https://ipcmen.com/dmidecode

    sudo MegaCli -PDList -aALL

    需要安装

    https://www.cnblogs.com/xth0331/p/9655593.html

    2.7 异常处理

    traceback使用

    import traceback
    def test():
        try:
            a = "dsadsa"
            int(a)
        except Exception as e:
            print(traceback.format_exc())
    
    test()
    

    src/plugins/init.py

    import traceback
    
    from lib.conf.config import settings
    import importlib
    import subprocess
    ### 管理插件信息的类
    class PluginsManager(object):
    
        def __init__(self, hostname=None):
            self.plugins_dict = settings.PLUGINS_DICT
            self.hostname = hostname
            self.debug = settings.DEBUG
            if settings.MODE == 'ssh':
                self.port = settings.SSH_PORT
                self.name = settings.SSH_USERNAME
                self.pwd  = settings.SSH_PASSWORD
    
        ### 读取配置文件中的pluginsdict, 并执行对应模块中的process方法
        def execute(self):
            response = {}
            for k, v in self.plugins_dict.items():
                ret = {"status":None, 'data':None}
                '''
                k:  board,...
                v:  src.plugins.board.Board   字符串
                '''
                try:
                    # 1. 导入模块路径
                    moudle_path, class_name = v.rsplit('.', 1)
                    # 2. 导入这个路径
                    moudle_name = importlib.import_module(moudle_path)
                    # 3. 导入对应模块下的类
                    classobj = getattr(moudle_name, class_name)
                    # 4. 执行类下面对应的process方法
                    res = classobj().process(self.__cmd_run, self.debug)
                    ret['status'] = 10000
                    ret['data'] = res
                except Exception as e:
                  	# hostname有值说明不是anget方案,是salstack或paramiko方案
                    ret['status'] = 10001
                    ret['data']=  "[%s] 采集 [%s] 出错了, 错误信息是:%s" % (self.hostname if self.hostname else "Agent", k, str(traceback.format_exc()))
                response[k] = ret
            return response
    
    
        def __cmd_run(self, cmd):
            if settings.MODE == 'agent':
                return self.__cmd_agent(cmd)
            elif settings.MODE == 'ssh':
                return self.__cmd_ssh(cmd)
            elif settings.MODE == 'salt':
                return self.__cmd_salt(cmd)
            else:
                print("只支持的模式有:agent/ssh/salt")
    
        def __cmd_agent(self, cmd):
            res = subprocess.getoutput(cmd)
            return res
    
        def __cmd_ssh(self, cmd):
            import paramiko
            # 创建SSH对象
            ssh = paramiko.SSHClient()
            # 允许连接不在know_hosts文件中的主机
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            # 连接服务器
            ssh.connect(hostname=self.hostname, port=self.port, username=self.name, password=self.pwd)
            # 执行命令
            stdin, stdout, stderr = ssh.exec_command(cmd)
            # 获取命令结果
            result = stdout.read()
            # 关闭连接
            ssh.close()
            return result
    
        def __cmd_salt(self, cmd):
            command = "salt %s cmd.run %s" % (self.hostname, cmd)
            res = subprocess.getoutput(command)
            return res
    

    2.8 把采集到的数据上传

    客户端

    ##  src/client
    import requests
    from lib.conf.config import settings
    from src.plugins import PluginsManager
    import os
    class Base():
        def post_data(self, server_info):
            requests.post(settings.API_URL, json=server_info)
    
    class Agent(Base):
        ### 收集数据并发送
        def collectAndPost(self):
            server_info = PluginsManager().execute()
    
            hostname = server_info['basic']['data']['hostname']  ### c10000.com
            res = open(os.path.join(settings.BASEDIR, 'config/cert'), 'r', encoding='utf-8').read()
    
            if not res.strip():
                #### 第一次采集, 将采集的hostname写入到一个文件中
                with open(os.path.join(settings.BASEDIR, 'config/cert'), 'w', encoding='utf-8') as fp:
                    fp.write(hostname)
            else:
                #### 第二次采集的时候, 永远以第一次文件中保存的主机名为标准
                server_info['basic']['data']['hostname'] = res
    
    
            for k, v in server_info.items():
                print(k, v)
            # requests.post(settings.API_URL, data=json.dumps(res))
            ### Content-Type':"application/json"
            self.post_data(server_info)
    
    
    class SSHSalt(Base):
        def get_hostnames(self):
            hostnames = requests.get(settings.API_URL)
            return ['c1.com', 'c2.com']
    
        def run(self, hostname):
            server_info = PluginsManager(hostname).execute()
            self.post_data(server_info)
    
        def collectAndPost(self):
            hostnames = self.get_hostnames()
            ### 单线程执行, 循环速度比较慢
            # for hostname in hostnames:
            #     server_info = PluginsManager(hostname).execute()
            #     self.post_data(server_info)
    
            ### 线程池的方式采集数据
            from concurrent.futures import ThreadPoolExecutor
            p = ThreadPoolExecutor(10)
            for hostname in hostnames:
                p.submit(self.run, hostname)
    
    # src/script.py
    from src.client import Agent
    from src.client import SSHSalt
    from lib.conf.config import settings
    
    def run():
        if settings.MODE == 'agent': 
            obj = Agent()
        else:# 不管salt和paramiko方式,都需要从服务器获取客户端ip地址
            obj = SSHSalt()
        obj.collectAndPost()
    
    # bin/start.py
    from src.script import run
    if __name__ == '__main__':
        run()
    

    服务端

    2.9 唯一标识的问题

    	
    # 目标:将变更的信息通过程序的比对, 记录下来
    	#第一天的时候:
    	# 采集数据:
    		{'status': 10000, 'data': {'os_platform': 'linux', 'os_version': 'CentOS release 6.6 (Final)
    Kernel 
     on an \m', 'hostname': 'c2.com'}}
    			
    	#API清洗的时候:
    		因为是第一次, 数据库中并没有采集的数据
    		数据入库:
    				server:1000条
    					id    sn        os_platform   os_version    disk_size
    					1     dsadsa       linux        CentOS        250G
    					........		
    	#第二天的时候(数据发生变化,应该比对):
    			#采集数据:	
    				{'status': 10000, 'data': {'os_platform': 'linux', 'os_version': 'CentOS release 6.6 (Final)
    Kernel 
     on an \m', 'hostname': 'c2.com'}}
    		
    				{'status': 10000, 'data': {'0': {'slot': '0', 'pd_type': 'SAS', 'capacity': '300G', 'model': 'SEAGATE ST300MM0006     LS08S0K2B5NV'}, '1': {'slot': '1', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006     LS08S0K2B5AH'}, '2': {'slot': '2', 'pd_type': 'SATA', 'capacity': '476.939', 'model': 'S1SZNSAFA01085L     Samsung SSD 850 PRO 512GB               EXM01B6Q'}, '3': {'slot': '3', 'pd_type': 'SATA', 'capacity': '476.939', 'model': 'S1AXNSAF912433K     Samsung SSD 840 PRO Series              DXM06B0Q'}, '4': {'slot': '4', 'pd_type': 'SATA', 'capacity': '476.939', 'model': 'S1AXNSAF303909M     Samsung SSD 840 PRO Series              DXM05B0Q'}, '5': {'slot': '5', 'pd_type': 'SATA', 'capacity': '476.939', 'model': 'S1AXNSAFB00549A     Samsung SSD 840 PRO Series              DXM06B0Q'}}}
    
         # API清洗的时候:
    				应该在新的POST数据中选取一个 唯一 的字段, 然后到数据库中作为where条件, 获取到对应的数据
    				
    			问题是  应该选取谁?
    				选取的是 sn 序列号(mac地址) 作为唯一的字段
    			用sn遇到的问题:
    				虚拟机和实体机共用一个sn, 导致数据不准确
    				
    # 解决的方案:
    				a. 如果公司不需要采集虚拟机的信息, 使用sn没有问题
    				b. 采用 hostname 作为唯一标识
    					- 是允许开发可以临时修改主机名的
            	-实现方案:
            		-1. 给这些服务器分配唯一的主机名
              	-2 将分配好的主机名录入到后台管理的DBserver表中
                -3. 将采集的client客户端代码, 运行一次
                -4 然后将得到的主机名地址保存到一个文件中
                第一天:
                1. 给这些服务器分配唯一的主机名
                2. 将分配好的主机名录入到后台管理的DBserver表中
                3. 将采集的client客户端代码, 运行一次,
                然后将得到的主机名地址保存到一个文件中
                第二天:
                hostname = server_info['basic']['data']['hostname']  ### c10000.com
                res = open(os.path.join(settings.BASEDIR, 'config/cert'), 'r', encoding='utf-8').read()
                if not res.strip():
                  #### 第一次采集, 将采集的hostname写入到一个文件中
                  with open(os.path.join(settings.BASEDIR, 'config/cert'), 'w', encoding='utf-8') as fp:
                    fp.write(hostname)
                    else:
                      #### 第二次采集的时候, 永远以第一次文件中保存的主机名为标准
                      server_info['basic']['data']['hostname'] = res
    

    代码实现

    # src/client.py
    # angent方案:第一次运行时取主机名,写到文件中,以后永远用主机名
    # ssh和salt方案,不需要此操作,因为一旦主机名改了,就连接不上了
    import requests
    from lib.conf.config import settings
    from src.plugins import PluginsManager
    import os
    class Base():
        def post_data(self, server_info):
            requests.post(settings.API_URL, json=server_info)
    
    class Agent(Base):
        ### 收集数据并发送
        def collectAndPost(self):
            server_info = PluginsManager().execute()
    
            hostname = server_info['basic']['data']['hostname']  ### c10000.com
            res = open(os.path.join(settings.BASEDIR, 'config/cert'), 'r', encoding='utf-8').read()
    
            if not res.strip():
                #### 第一次采集, 将采集的hostname写入到一个文件中
                with open(os.path.join(settings.BASEDIR, 'config/cert'), 'w', encoding='utf-8') as fp:
                    fp.write(hostname)
            else:
                #### 第二次采集的时候, 永远以第一次文件中保存的主机名为标准
                server_info['basic']['data']['hostname'] = res
    
    
            for k, v in server_info.items():
                print(k, v)
            # requests.post(settings.API_URL, data=json.dumps(res))
            ### Content-Type':"application/json"
            self.post_data(server_info)
    
    
    class SSHSalt(Base):
     	pass
    

    2.10 API的验证

    第一种方式

    # 客户端:
    
    #### 第一种方式
    import  requests
    token = "dsabdshanbdjsanjdsanjds"
    #### 切记, 进行token验证的时候, 一定是将token写在http的请求头中
    res = requests.get("http://127.0.0.1:8000/getInfo/", headers = {"token":token})
    print(res.text)
    
    # 服务端:
    
    token = request.META.get('HTTP_TOKEN')
    server_token = "dsabdshanbdjsanjdsanjdsa"
    if token != server_token:
    return HttpResponse('token值是错误的!')
    

    第二种方式

    ## 客户端:	
    import  requests
    token = "dsabdshanbdjsanjdsanjds"
    
    import time
    client_time = time.time()
    tmp = "%s|%s" % (token, client_time)
    
    ##### 加密
    import hashlib
    m = hashlib.md5()
    m.update(bytes(tmp, encoding='utf8'))
    res = m.hexdigest()
    client_md5_token = "%s|%s" % (res, client_time)
    
    #### 切记, 进行token验证的时候, 一定是将token写在http的请求头中
    data = requests.get("http://127.0.0.1:8000/getInfo/", headers = {"token":client_md5_token})
    print(data.text)
    
    			
    # 服务端:
    server_token = "dsabdshanbdjsanjdsanjds"
    server_time = time.time()
    client_md5_header = request.META.get('HTTP_TOKEN')
    client_md5_token, client_time = client_md5_header.split('|')
    client_time = float(client_time)
    
    if server_time - client_time > 10:
      return HttpResponse(' 时间太久了.....')
    
    tmp = "%s|%s" % (server_token, client_time)
    m = hashlib.md5()
    m.update(bytes(tmp, encoding='utf-8'))
    server_md5_token = m.hexdigest()
    
    if server_md5_token != client_md5_token:
      return HttpResponse('修改了token')
    

    三 服务端编写

    3.1 后台表结构

    Disk表:

    NIC表:

    Memory表:

    Server表:机器位置信息,在哪个机房,机房基层,机柜位置,部署时间这些属性手动录入

    跟上面三个表是一对多

    IDC表:机房表,跟Server是一对多

    BusinessUnit表:业务线(产品线)表,跟server是一对多

    Tag表:标签表,跟Server是多对多

    UserInfo表:用户表,分产品线表是多对多

    UserGroup表:用户组表,跟用户多对多

    AssetRecord表:资产变更记录表,server跟AssetRecord是一对多

    ErrorLog表:错误日志表,server跟errorlog是一对多

    from django.db import models
    
    class UserProfile(models.Model):
        """
        用户信息
        """
        name = models.CharField(u'姓名', max_length=32)
        email = models.EmailField(u'邮箱')
        phone = models.CharField(u'座机', max_length=32)
        mobile = models.CharField(u'手机', max_length=32)
        password = models.CharField(u'密码', max_length=64)
    
        class Meta:
            verbose_name_plural = "用户表"
    
        def __str__(self):
            return self.name
    
    
    
    class UserGroup(models.Model):
        """
        用户组
        """
        name = models.CharField(max_length=32, unique=True)
        users = models.ManyToManyField('UserProfile')
    
        class Meta:
            verbose_name_plural = "用户组表"
    
        def __str__(self):
            return self.name
    
    
    class BusinessUnit(models.Model):
        """
        业务线
        """
        name = models.CharField('业务线', max_length=64, unique=True)
        contact = models.ForeignKey('UserGroup', verbose_name='业务联系人', related_name='c')
        manager = models.ForeignKey('UserGroup', verbose_name='系统管理员', related_name='m')
    
        class Meta:
            verbose_name_plural = "业务线表"
    
        def __str__(self):
            return self.name
    
    
    class IDC(models.Model):
        """
        机房信息
        """
        name = models.CharField('机房', max_length=32)
        floor = models.IntegerField('楼层', default=1)
    
        class Meta:
            verbose_name_plural = "机房表"
    
        def __str__(self):
            return self.name
    
    
    class Tag(models.Model):
        """
        资产标签
        """
        name = models.CharField('标签', max_length=32, unique=True)
    
        class Meta:
            verbose_name_plural = "标签表"
    
        def __str__(self):
            return self.name
    
    
    
    class Server(models.Model):
        """
        服务器信息
        """
        device_type_choices = (
            (1, '服务器'),
            (2, '交换机'),
            (3, '防火墙'),
        )
        device_status_choices = (
            (1, '上架'),
            (2, '在线'),
            (3, '离线'),
            (4, '下架'),
        )
    
        device_type_id = models.IntegerField('服务器类型',choices=device_type_choices, default=1)
        device_status_id = models.IntegerField('服务器状态',choices=device_status_choices, default=1)
    
        cabinet_num = models.CharField('机柜号', max_length=30, null=True, blank=True)
        cabinet_order = models.CharField('机柜中序号', max_length=30, null=True, blank=True)
    
        idc = models.ForeignKey('IDC', verbose_name='IDC机房', null=True, blank=True)
        business_unit = models.ForeignKey('BusinessUnit', verbose_name='属于的业务线', null=True, blank=True)
    
        tag = models.ManyToManyField('Tag')
    
        hostname = models.CharField('主机名',max_length=128, unique=True)
        sn = models.CharField('SN号', max_length=64, db_index=True)
        manufacturer = models.CharField(verbose_name='制造商', max_length=64, null=True, blank=True)
        model = models.CharField('型号', max_length=64, null=True, blank=True)
    
        manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True)
    
        os_platform = models.CharField('系统', max_length=16, null=True, blank=True)
        os_version = models.CharField('系统版本', max_length=16, null=True, blank=True)
    
        cpu_count = models.IntegerField('CPU个数', null=True, blank=True)
        cpu_physical_count = models.IntegerField('CPU物理个数', null=True, blank=True)
        cpu_model = models.CharField('CPU型号', max_length=128, null=True, blank=True)
    
        create_at = models.DateTimeField(auto_now_add=True, blank=True)
    
        class Meta:
            verbose_name_plural = "服务器表"
    
        def __str__(self):
            return self.hostname
    
    
    class Disk(models.Model):
        """
        硬盘信息
        """
        slot = models.CharField('插槽位', max_length=8)
        model = models.CharField('磁盘型号', max_length=32)
        capacity = models.CharField('磁盘容量GB', max_length=32)
        pd_type = models.CharField('磁盘类型', max_length=32)
        server_obj = models.ForeignKey('Server',related_name='disk')
    
        class Meta:
            verbose_name_plural = "硬盘表"
    
        def __str__(self):
            return self.slot
    
    
    class NIC(models.Model):
        """
        网卡信息
        """
        name = models.CharField('网卡名称', max_length=128)
        hwaddr = models.CharField('网卡mac地址', max_length=64)
        netmask = models.CharField(max_length=64)
        ipaddrs = models.CharField('ip地址', max_length=256)
        up = models.BooleanField(default=False)
        server_obj = models.ForeignKey('Server',related_name='nic')
    
    
        class Meta:
            verbose_name_plural = "网卡表"
    
        def __str__(self):
            return self.name
    
    
    class Memory(models.Model):
        """
        内存信息
        """
        slot = models.CharField('插槽位', max_length=32)
        manufacturer = models.CharField('制造商', max_length=32, null=True, blank=True)
        model = models.CharField('型号', max_length=64)
        capacity = models.FloatField('容量', null=True, blank=True)
        sn = models.CharField('内存SN号', max_length=64, null=True, blank=True)
        speed = models.CharField('速度', max_length=16, null=True, blank=True)
    
        server_obj = models.ForeignKey('Server',related_name='memory')
    
    
        class Meta:
            verbose_name_plural = "内存表"
    
        def __str__(self):
            return self.slot
    
    class AssetRecord(models.Model):
        """
        资产变更记录,creator为空时,表示是资产汇报的数据。
        """
        asset_obj = models.ForeignKey('Server', related_name='ar')
        content = models.TextField(null=True)# 新增硬盘
        creator = models.ForeignKey('UserProfile', null=True, blank=True) #
        create_at = models.DateTimeField(auto_now_add=True)
    
    
        class Meta:
            verbose_name_plural = "资产记录表"
    
        def __str__(self):
            return "%s-%s-%s" % (self.asset_obj.idc.name, self.asset_obj.cabinet_num, self.asset_obj.cabinet_order)
    
    
    class ErrorLog(models.Model):
        """
        错误日志,如:agent采集数据错误 或 运行错误
        """
        asset_obj = models.ForeignKey('Server', null=True, blank=True)
        title = models.CharField(max_length=16)
        content = models.TextField()
        create_at = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            verbose_name_plural = "错误日志表"
    
        def __str__(self):
            return self.title
          
    # admin管理
    from repository import models
    admin.site.register(models.Server)
    admin.site.register(models.UserProfile)
    admin.site.register(models.UserGroup)
    admin.site.register(models.BusinessUnit)
    admin.site.register(models.IDC)
    admin.site.register(models.Tag)
    admin.site.register(models.Disk)
    admin.site.register(models.Memory)
    admin.site.register(models.NIC)
    admin.site.register(models.AssetRecord)
    admin.site.register(models.ErrorLog)
    
    # 录入信息
    # 录入三条业务线:互娱部,新闻部,云计算部
    # 录入用户组:A组,B组,C组
    # 录入管理员:张三,李四,王五
    # 录入Server数据:服务器,上架,机柜号13,机柜中序号32,IDC机房,业务线,标签,主机名(c2.com)
    # 录入IDC机房:世纪互联,神州
    # 录入标签:web,db,cache
    

    3.2 资产清洗录入(以硬盘为例)

    # 新增:new-old
    # 删除:old-new
    # 更新:交集
    
    # 差集
    new_slot_list={0,1,2}
    old_slot_list={0,1}
    # 差集
    res=new_slot_list-old_slot_list
    print(res)
    # 或者
    res=new_slot_list.difference(old_slot_list)
    print(res)
    
    # 交集
    print(new_slot_list & old_slot_list)
    # 或者
    print(new_slot_list.intersection(old_slot_list))
    
    def getInfo(request):
    
        if request.method == 'POST':
            data = request.body
            # print(data)
            data = json.loads(data)
    
            #### 通过主机名获取老的数据对应的记录
            hostname = data['basic']['data']['hostname']
    
            old_server_info = models.Server.objects.filter(hostname=hostname).first() ## obj
    
            if not old_server_info:
                return HttpResponse('资产不存在')
    
    
            #### 以分析disk硬盘数据为例, 进行比对分析
    
            #### 如果采集出错的话, 记录错误的信息
            if data['disk']['status'] != 10000:
                models.ErrorLog.objects.create(asset_obj=old_server_info, title = "%s 采集硬盘出错了" % (hostname), content=data['disk']['data'])
    
            '''
                {
                    '0': {'slot': '0', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006     LS08S0K2B5NV'}, 
                    '1': {'slot': '1', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006     LS08S0K2B5AH'}, 
                    '2': {'slot': '2', 'pd_type': 'SATA', 'capacity': '476.939', 'model': 'S1SZNSAFA01085L     Samsung SSD 850 PRO 512GB               EXM01B6Q'}, 
                }
            '''
            new_disk_info = data['disk']['data']
    
            '''
                [
                    obj(slot:0, pd_type:SAS,......),
                    obj(slot:1, pd_type:SATA,......),
                    ....
                ]
            '''
            old_disk_info = models.Disk.objects.filter(server_obj=old_server_info).all() ## []
    
            new_slot_list = list(new_disk_info.keys())
            old_slot_list = []
            for obj in old_disk_info:
                old_slot_list.append(obj.slot)
    
            '''
            new_slot_list = [0,2]
            old_slot_list = [0,1]
            新增: new_slot_list - old_slot_list = 2   
            删除: old_slot_list - new_slot_list = 1
            更新: 交集
            '''
            #### 增加slot
            add_slot_list = set(new_slot_list).difference(set(old_slot_list))
    
            if add_slot_list:
                record_list = []
                for slot in add_slot_list:
                    # {'slot': '0', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006     LS08S0K2B5NV'}
                    disk_res = new_disk_info[slot]
                    tmp = "添加插槽是:{slot}, 磁盘类型是:{pd_type}, 磁盘容量是:{capacity}, 磁盘的型号:{model}".format(**disk_res)
                    disk_res['server_obj'] = old_server_info
                    record_list.append(tmp)
                    models.Disk.objects.create(**disk_res)
    
                ### 将变更新的信息添加到变更记录表中
                record_str = ";".join(record_list)
                models.AssetRecord.objects.create(asset_obj=old_server_info, content=record_str)
    
            #### 删除slot
            del_slot_list = set(old_slot_list).difference(set(new_slot_list))
            if del_slot_list:
                record_str = "删除的槽位是:%s" % (";".join(del_slot_list))
                models.Disk.objects.filter(slot__in=del_slot_list, server_obj=old_server_info).delete()
                models.AssetRecord.objects.create(asset_obj=old_server_info, content=record_str)
    
    
            #### 更新硬盘数据
            up_solt_list = set(new_slot_list).intersection(set(old_slot_list))
            if up_solt_list:
                record_list = []
                for slot in up_solt_list:
                    ## 新的:'0': {'slot': '0', 'pd_type': 'SAS', 'capacity': '500G', 'model': 'SEAGATE ST300MM0006     LS08S0K2B5NV'}
                    new_disk_row = new_disk_info[slot]
                    ### 老的:obj(slot:0, pd_type:SAS,.....)
                    old_disk_row = models.Disk.objects.filter(slot=slot, server_obj=old_server_info).first()
                    for k, new_v in new_disk_row.items():
                        '''
                        k:      slot, pd_type, capacity,...
                        new_v:   0     SAS       279.396,....
                        '''
                        ### 利用反射
                        ### 1. 先从老的数据中心获取老的数据
                        old_v = getattr(old_disk_row, k)
                        ### 2. 判断老的数据和新的数据是否相同
                        if new_v != old_v:
                            tmp = "槽位%s, %s由原来的%s变成了%s" % (slot, k, old_v, new_v)
                            record_list.append(tmp)
                            ### 3. 将新的数据设置回到老的数据行对象中
                            setattr(old_disk_row, k, new_v)
                    ### 4. 调用save, 保存
                    old_disk_row.save()
    
                if record_list:
                    models.AssetRecord.objects.create(asset_obj=old_server_info, content=";".join(record_list))
    
    
            return HttpResponse('ok')
        else:
            ### 第一种方式的判断
            # if token != server_token:
            #     return HttpResponse('token值是错误的!')
    
            ### 连接数据库获取主机名列表
            token = request.META.get('HTTP_TOKEN')
            client_md5_token, client_time = token.split('|')
    
            client_time = float(client_time)
    
            import time
            server_time = time.time()
            if server_time - client_time > 10:
                return HttpResponse('第一关【超时了】')
    
            server_token = "dsabdshanbdjsanjdsanjdsa"
    
            tmp = "%s|%s" % (server_token, client_time)
            import hashlib
            m = hashlib.md5()
            m.update(bytes(tmp, encoding='utf8'))
            server_md5_token = m.hexdigest()
            if server_md5_token != client_md5_token:
                return HttpResponse('第二关【数据被修改过了】')
    
    
            #### 第三关, 连接redis
    
            ### 第一次来的时候, 先去redis中判断, client_md5_token 是否在redis中,
            ### 如果在redis中, 则代表已经访问过了, return 回去
            ### 如果不在redis中, 则第一次访问, 添加到redis中, 并且设置过期时间 10s
    
    
            return HttpResponse('非常重要的数据')
    

    3.3 前后端混合开发之layui

    https://www.layui.com/doc/element/layout.html#adminhttps://www.layui.com/doc/element/layout.html#admin
    

    3.4 前后端混合开发之xadmin

    # adminx.py
    import xadmin
    
    from repository import models
    
    
    class DiskAdmin(object):
        list_display = ['id','slot' ,'model','capacity','pd_type','server_obj']
    
        search_fields = ['id', 'slot' ,'model','capacity','pd_type']
        # list_editable = ['name' ,'email','phone','mobile']
        # list_filter = ['name' ,'email','phone','mobile']
        # list_filter = ['oid','user' ,'odate','oisPay','ototal','oadress']
    
    class ServerAdmin(object):
        list_display = ['id', 'device_type_id', 'device_status_id', 'idc', 'business_unit', 'hostname', 'create_at']
    
        show_detail_fields = ['hostname']
        # search_fields = ['id', 'slot', 'model', 'capacity', 'pd_type']
        # data_charts = {
        #     "user_count": {'title': u"服务器分布", "x-field": "idc", "y-field": ("business_unit",),},
        #     # "avg_count": {'title': u"Avg Report", "x-field": "date", "y-field": ('avg_count',), "order": ('date',)}
        # }
        # list_per_page = 2
        data_charts = {
            "host_service_type_counts": {
                'title': '部门机器使用情况',
                'x-field': "business_unit",
                'y-field': ("business_unit"),
                'option': {
                    "series": {"bars": {"align": "center", "barWidth": 0.8, "show": True}},
                    "xaxis": {"aggregate": "count", "mode": "categories"}
                },
            },
            "host_idc_counts": {
                'title': '机房统计',
                'x-field': "idc",
                'y-field': ("idc",),
                'option': {
                    "series": {"bars": {"align": "center", "barWidth": 0.3, "show": True}},
                    "xaxis": {"aggregate": "count", "mode": "categories"}
                }
            }
        }
    
    
    class IDCAdmin(object):
        list_display = ['id', 'name', 'floor']
    
        show_detail_fields = ['name']
        # search_fields = ['id', 'slot', 'model', 'capacity', 'pd_type']
    
    
    xadmin.site.register(models.Disk,DiskAdmin)
    xadmin.site.register(models.Server,ServerAdmin)
    xadmin.site.register(models.IDC,IDCAdmin)
    
    

    3.5 前后端分离之vue-admin

    # 介绍地址
    https://panjiachen.github.io/vue-element-admin-site/zh/guide/
      
    # 集成版本(高级版本)
    https://github.com/PanJiaChen/vue-element-admin
    # 演示地址
    https://github.com/PanJiaChen/vue-element-admin/blob/master/README.zh-CN.md
      
    # 基础版本
    https://github.com/PanJiaChen/vue-admin-template
      
    # 桌面版
    https://github.com/PanJiaChen/electron-vue-admin
      
    

    3.6 图表展示

    Highchars

    https://www.highcharts.com.cn/
    
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <title>layout 后台大布局 - Layui</title>
        <link rel="stylesheet" href="/static/lib/layui/css/layui.css">
        <script src="http://cdn.highcharts.com.cn/highcharts/highcharts.js"></script>
    {#    <script src="/static/js/hichars.js"></script>#}
    </head>
    <body class="layui-layout-body">
    <div class="layui-layout layui-layout-admin">
        <div class="layui-header">
            <div class="layui-logo">layui 后台布局</div>
            <!-- 头部区域(可配合layui已有的水平导航) -->
            <ul class="layui-nav layui-layout-left">
                <li class="layui-nav-item"><a href="">控制台</a></li>
                <li class="layui-nav-item"><a href="">商品管理</a></li>
                <li class="layui-nav-item"><a href="">用户</a></li>
                <li class="layui-nav-item">
                    <a href="javascript:;">其它系统</a>
                    <dl class="layui-nav-child">
                        <dd><a href="">邮件管理</a></dd>
                        <dd><a href="">消息管理</a></dd>
                        <dd><a href="">授权管理</a></dd>
                    </dl>
                </li>
            </ul>
            <ul class="layui-nav layui-layout-right">
                <li class="layui-nav-item">
                    <a href="javascript:;">
                        <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
                        贤心
                    </a>
                    <dl class="layui-nav-child">
                        <dd><a href="">基本资料</a></dd>
                        <dd><a href="">安全设置</a></dd>
                    </dl>
                </li>
                <li class="layui-nav-item"><a href="">退了</a></li>
            </ul>
        </div>
    
        <div class="layui-side layui-bg-black">
            <div class="layui-side-scroll">
                <!-- 左侧导航区域(可配合layui已有的垂直导航) -->
                <ul class="layui-nav layui-nav-tree" lay-filter="test">
                    <li class="layui-nav-item layui-nav-itemed">
                        <a class="" href="javascript:;">所有商品</a>
                        <dl class="layui-nav-child">
                            <dd><a href="javascript:;">列表一</a></dd>
                            <dd><a href="javascript:;">列表二</a></dd>
                            <dd><a href="javascript:;">列表三</a></dd>
                            <dd><a href="">超链接</a></dd>
                        </dl>
                    </li>
                    <li class="layui-nav-item">
                        <a href="javascript:;">解决方案</a>
                        <dl class="layui-nav-child">
                            <dd><a href="javascript:;">列表一</a></dd>
                            <dd><a href="javascript:;">列表二</a></dd>
                            <dd><a href="">超链接</a></dd>
                        </dl>
                    </li>
                    <li class="layui-nav-item"><a href="">云市场</a></li>
                    <li class="layui-nav-item"><a href="">发布商品</a></li>
                </ul>
            </div>
        </div>
    
        <div class="layui-body">
            <!-- 内容主体区域 -->
            <div style="padding: 15px;">
              <div id="container" style="max-800px;height:400px"></div>
    
    
            </div>
        </div>
    
        <div class="layui-footer">
            <!-- 底部固定区域 -->
            © layui.com - 底部固定区域
        </div>
    </div>
    <script src="/static/lib/layui/layui.js"></script>
    <script>
        //JavaScript代码区域
        layui.use('element', function () {
            var element = layui.element;
    
        });
    
        var chart = Highcharts.chart('container', {
       title: {
          text: '用户活跃量'
       },
       yAxis: {
          title: {
             text: '用户人数'
          }
       },
       legend: {
          layout: 'vertical',
          align: 'right',
          verticalAlign: 'middle'
       },
       plotOptions: {
          series: {
             label: {
                connectorAllowed: false
             },
             pointStart: 1
          }
       },
       series: [{
          name: '用户登录系统人数',
          data: [10, 20, 14, 30, 55, 77, 99, 12]
       },],
       responsive: {
          rules: [{
             condition: {
                maxWidth: 500
             },
             chartOptions: {
                legend: {
                   layout: 'horizontal',
                   align: 'center',
                   verticalAlign: 'bottom'
                }
             }
          }]
       }
    });
    </script>
    </body>
    </html>
    

    echars

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <title>layout 后台大布局 - Layui</title>
        <link rel="stylesheet" href="/static/lib/layui/css/layui.css">
        <script src="https://cdn.jsdelivr.net/npm/echarts@5.0.2/dist/echarts.min.js"></script>
    </head>
    <body class="layui-layout-body">
    <div class="layui-layout layui-layout-admin">
        <div class="layui-header">
            <div class="layui-logo">layui 后台布局</div>
            <!-- 头部区域(可配合layui已有的水平导航) -->
            <ul class="layui-nav layui-layout-left">
                <li class="layui-nav-item"><a href="">控制台</a></li>
                <li class="layui-nav-item"><a href="">商品管理</a></li>
                <li class="layui-nav-item"><a href="">用户</a></li>
                <li class="layui-nav-item">
                    <a href="javascript:;">其它系统</a>
                    <dl class="layui-nav-child">
                        <dd><a href="">邮件管理</a></dd>
                        <dd><a href="">消息管理</a></dd>
                        <dd><a href="">授权管理</a></dd>
                    </dl>
                </li>
            </ul>
            <ul class="layui-nav layui-layout-right">
                <li class="layui-nav-item">
                    <a href="javascript:;">
                        <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
                        贤心
                    </a>
                    <dl class="layui-nav-child">
                        <dd><a href="">基本资料</a></dd>
                        <dd><a href="">安全设置</a></dd>
                    </dl>
                </li>
                <li class="layui-nav-item"><a href="">退了</a></li>
            </ul>
        </div>
    
        <div class="layui-side layui-bg-black">
            <div class="layui-side-scroll">
                <!-- 左侧导航区域(可配合layui已有的垂直导航) -->
                <ul class="layui-nav layui-nav-tree" lay-filter="test">
                    <li class="layui-nav-item layui-nav-itemed">
                        <a class="" href="javascript:;">所有商品</a>
                        <dl class="layui-nav-child">
                            <dd><a href="javascript:;">列表一</a></dd>
                            <dd><a href="javascript:;">列表二</a></dd>
                            <dd><a href="javascript:;">列表三</a></dd>
                            <dd><a href="">超链接</a></dd>
                        </dl>
                    </li>
                    <li class="layui-nav-item">
                        <a href="javascript:;">解决方案</a>
                        <dl class="layui-nav-child">
                            <dd><a href="javascript:;">列表一</a></dd>
                            <dd><a href="javascript:;">列表二</a></dd>
                            <dd><a href="">超链接</a></dd>
                        </dl>
                    </li>
                    <li class="layui-nav-item"><a href="">云市场</a></li>
                    <li class="layui-nav-item"><a href="">发布商品</a></li>
                </ul>
            </div>
        </div>
    
        <div class="layui-body">
            <!-- 内容主体区域 -->
            <div style="padding: 15px;">
                <div id="main" style=" 600px;height:400px;"></div>
    
    
            </div>
        </div>
    
        <div class="layui-footer">
            <!-- 底部固定区域 -->
            © layui.com - 底部固定区域
        </div>
    </div>
    <script src="/static/lib/layui/layui.js"></script>
    <script>
        //JavaScript代码区域
        layui.use('element', function () {
            var element = layui.element;
    
        });
        var myChart = echarts.init(document.getElementById('main'));
        option = {
            xAxis: {
                type: 'category',
                data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
            },
            yAxis: {
                type: 'value'
            },
            series: [{
                data: [150, 230, 224, 218, 135, 147, 260],
                type: 'line'
            }]
        };
        myChart.setOption(option);
    
    </script>
    </body>
    </html>
    
  • 相关阅读:
    实现两个整数变量的互换
    Js中replace替换所有*
    下载win10系统
    Linux网络基本网络配置方法介绍
    搭建Linux虚拟服务器
    解决SVN Cleanup错误: Failed to run the WC DB work queue associated with
    详细QRCode生成二维码和下载实现案例
    Win10中Vue.js的安装和项目搭建
    什么是Docker,它可干什么?
    Win10下搭建Git服务器
  • 原文地址:https://www.cnblogs.com/liuqingzheng/p/14527292.html
Copyright © 2020-2023  润新知