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('\n')
# 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)\nKernel \r on an \m",
                'hostname': 'c2000.com'
            }
        else:
            output = {
                'os_platform': command_func("uname").strip(),
                'os_version': command_func("cat /etc/issue").strip().split('\n')[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('\n'):
            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('\n\n'):
            for row_line in item.split('\n'):
                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("\n\n\n\n"):
            result.append(row_line)
        for item in result:
            temp_dict = {}
            for row in item.split('\n'):
                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('\n\t')
            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 + '\n' + 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('\r?\n\\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)\nKernel \r 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)\nKernel \r 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-width: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="width: 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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM