CMDB项目

一 CMDB简介

https://www.wangt.cc/2021/03/cmdb%E9%A1%B9%E7%9B%AE/

1.1 什么是CMDB?

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

1.2 CMDB包含的功能

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

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

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

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

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

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

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

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

1.3 实现的四种方式

1.3.1 Agent实现方式

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

CMDB项目

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

  1. #linux
  2. import subprocess
  3. import re
  4. res = subprocess.getoutput("ifconfig")
  5. print(res)
  6. ip=re.findall('inet (.*?) netmask',res)
  7. print(ip)
  8. # windows
  9. import subprocess
  10. import re
  11. res=subprocess.getoutput('ipconfig')
  12. print(res)
  13. ip=re.findall('IPv4 地址 . . . . . . . . . . . . : (.*)',res)
  14. print(ip)

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

使用crontab定时执行python脚本

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

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

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

CMDB项目

优点:无Agent

缺点:速度慢

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

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

1.3.3 saltstack方式

CMDB项目

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

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

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

优点:快,开发成本低

缺点:依赖于第三方工具

salstack的安装和配置

1.安装和配置

  1. master端:
  2. """
  3. 1. 安装salt-master
  4. yum install salt-master
  5. 2. 修改配置文件:/etc/salt/master
  6. interface: 0.0.0.0 # 表示Master的IP
  7. 3. 启动
  8. service salt-master start
  9. """
  10. slave端:
  11. """
  12. 1. 安装salt-minion
  13. yum install salt-minion
  14. 2. 修改配置文件 /etc/salt/minion
  15. master: 10.211.55.4 # master的地址
  16. master:
  17. - 10.211.55.4
  18. - 10.211.55.5
  19. random_master: True
  20. id: c2.salt.com # 客户端在salt-master中显示的唯一ID
  21. 3. 启动
  22. service salt-minion start

2.授权

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

3.执行命令

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

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

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

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

二 三种方案客户端编写

2.1 目录结构划分

  1. autoclient # 项目名
  2. -bin # 启动文件路径
  3. -start.py # 启动文件
  4. -config # 配置文件路径
  5. -cert # 私钥
  6. -custom_settings.py # 用户自定义配置
  7. -files # 测试数据文件
  8. -board.out
  9. -cpuinfo.out
  10. -disk.out
  11. -memory.out
  12. -nic.out
  13. -lib # 库文件夹
  14. -conf # 配置信息文件夹
  15. -config.py # 配置类
  16. -global_settings.py # 全局常量配置
  17. -convert.py # 公共方法
  18. -src # 源文件
  19. -plugins # 插件
  20. -__init__.py # 初始化文件
  21. -basic.py
  22. -board.py
  23. -cpu.py
  24. -disk.py
  25. -memory.py
  26. -nic.py
  27. script.py # 脚本文件
  28. client.py # 客户端类
  29. tests # 测试文件夹
  30. # 总结:bin,config,files,lib,src几个文件夹

2.2 仿django配置文件

custom_settings.py

  1. # 用户配置
  2. PORT = 22
  3. USER = 'lqz'

global_settings.py

  1. #### 全局配置
  2. PORT = 22
  3. USER = 'root'

config.py

  1. from config import custom_settings
  2. from . import global_settings
  3. class Settings():
  4. def __init__(self):
  5. #### 全局配置
  6. for key in dir(global_settings):
  7. if key.isupper():
  8. #### 获取key所对应的值
  9. v = getattr(global_settings, key)
  10. #### 设置key以及值到当前的setting对象
  11. setattr(self, key, v)
  12. #### 自定制配置
  13. for key in dir(custom_settings):
  14. if key.isupper():
  15. #### 获取key所对应的值
  16. v = getattr(custom_settings, key)
  17. #### 设置key以及值到当前的setting对象
  18. setattr(self, key, v)
  19. settings = Settings()

2.3 可插拔式配置

custom_settings.py

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

src/plugins/__init__.py

  1. import traceback
  2. from lib.conf.config import settings
  3. import importlib
  4. import subprocess
  5. ### 管理插件信息的类
  6. class PluginsManager(object):
  7. def __init__(self, hostname=None):
  8. pass
  9. ### 读取配置文件中的pluginsdict, 并执行对应模块中的process方法
  10. def execute(self):
  11. response = {}
  12. for k, v in self.plugins_dict.items():
  13. ret = {"status":None, 'data':None}
  14. '''
  15. k: board,...
  16. v: src.plugins.board.Board 字符串
  17. '''
  18. try:
  19. # 1. 导入模块路径
  20. moudle_path, class_name = v.rsplit('.', 1)
  21. # 2. 导入这个路径
  22. moudle_name = importlib.import_module(moudle_path)
  23. # 3. 导入对应模块下的类
  24. classobj = getattr(moudle_name, class_name)
  25. # 4. 执行类下面对应的process方法
  26. res = classobj().process()
  27. except Exception as e:
  28. pass
  29. return response

src/plugins/cpu.py

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. import os
  4. from lib.conf.config import settings
  5. class Cpu(object):
  6. def __init__(self):
  7. pass
  8. @classmethod
  9. def initial(cls):
  10. return cls()
  11. def process(self, command_func, debug):
  12. print('cpu print')

src/plugins/disk.py

  1. import os
  2. from lib.conf.config import settings
  3. class Cpu(object):
  4. def __init__(self):
  5. pass
  6. @classmethod
  7. def initial(cls):
  8. return cls()
  9. def process(self, command_func, debug):
  10. print('disk print')

2.4 冗余代码抽取

继承方式

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

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

  1. import traceback
  2. from lib.conf.config import settings
  3. import importlib
  4. import subprocess
  5. ### 管理插件信息的类
  6. class PluginsManager(object):
  7. def __init__(self, hostname=None):
  8. self.plugins_dict = settings.PLUGINS_DICT
  9. self.hostname = hostname # 采集客户端的地址
  10. self.debug = settings.DEBUG
  11. if settings.MODE == 'ssh': # ssh方式才需要端口,用户名,密码,这些应该放到配置文件中
  12. self.port = settings.SSH_PORT
  13. self.name = settings.SSH_USERNAME
  14. self.pwd = settings.SSH_PASSWORD
  15. ### 读取配置文件中的pluginsdict, 并执行对应模块中的process方法
  16. def execute(self):
  17. response = {}
  18. for k, v in self.plugins_dict.items():
  19. ret = {"status":None, 'data':None}
  20. '''
  21. k: board,...
  22. v: src.plugins.board.Board 字符串
  23. '''
  24. try:
  25. # 1. 导入模块路径
  26. moudle_path, class_name = v.rsplit('.', 1)
  27. # 2. 导入这个路径
  28. moudle_name = importlib.import_module(moudle_path)
  29. # 3. 导入对应模块下的类
  30. classobj = getattr(moudle_name, class_name)
  31. # 4. 执行类下面对应的process方法
  32. res = classobj().process(self.__cmd_run, self.debug)
  33. ret['status'] = 10000
  34. ret['data'] = res
  35. except Exception as e:
  36. ret['status'] = 10001
  37. ret['data']= "[%s] 采集 [%s] 出错了, 错误信息是:%s" % (self.hostname if self.hostname else "Agent", k, str(traceback.format_exc()))
  38. response[k] = ret
  39. return response
  40. def __cmd_run(self, cmd):
  41. if settings.MODE == 'agent':
  42. return self.__cmd_agent(cmd)
  43. elif settings.MODE == 'ssh':
  44. return self.__cmd_ssh(cmd)
  45. elif settings.MODE == 'salt':
  46. return self.__cmd_salt(cmd)
  47. else:
  48. print("只支持的模式有:agent/ssh/salt")
  49. def __cmd_agent(self, cmd):
  50. res = subprocess.getoutput(cmd)
  51. return res
  52. def __cmd_ssh(self, cmd):
  53. import paramiko
  54. # 创建SSH对象
  55. ssh = paramiko.SSHClient()
  56. # 允许连接不在know_hosts文件中的主机
  57. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  58. # 连接服务器
  59. ssh.connect(hostname=self.hostname, port=self.port, username=self.name, password=self.pwd)
  60. # 执行命令
  61. stdin, stdout, stderr = ssh.exec_command(cmd)
  62. # 获取命令结果
  63. result = stdout.read()
  64. # 关闭连接
  65. ssh.close()
  66. return result
  67. def __cmd_salt(self, cmd):
  68. command = "salt %s cmd.run %s" % (self.hostname, cmd)
  69. res = subprocess.getoutput(command)
  70. return res

在cpu.py disk.py中编写

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

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

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

2.6 代码整合

  1. plugins
  2. -__init__.py
  3. -basic.py
  4. -board.py
  5. -cpu.py
  6. -disk.py
  7. -memory.py
  8. -nic.py
  1. #__init__.py
  2. import traceback
  3. from lib.conf.config import settings
  4. import importlib
  5. import subprocess
  6. ### 管理插件信息的类
  7. class PluginsManager(object):
  8. def __init__(self, hostname=None):
  9. self.plugins_dict = settings.PLUGINS_DICT
  10. self.hostname = hostname
  11. self.debug = settings.DEBUG
  12. if settings.MODE == 'ssh':
  13. self.port = settings.SSH_PORT
  14. self.name = settings.SSH_USERNAME
  15. self.pwd = settings.SSH_PASSWORD
  16. ### 读取配置文件中的pluginsdict, 并执行对应模块中的process方法
  17. def execute(self):
  18. response = {}
  19. for k, v in self.plugins_dict.items():
  20. ret = {"status":None, 'data':None}
  21. '''
  22. k: board,...
  23. v: src.plugins.board.Board 字符串
  24. '''
  25. try:
  26. # 1. 导入模块路径
  27. moudle_path, class_name = v.rsplit('.', 1)
  28. # 2. 导入这个路径
  29. moudle_name = importlib.import_module(moudle_path)
  30. # 3. 导入对应模块下的类
  31. classobj = getattr(moudle_name, class_name)
  32. # 4. 执行类下面对应的process方法
  33. res = classobj().process(self.__cmd_run, self.debug)
  34. ret['status'] = 10000
  35. ret['data'] = res
  36. except Exception as e:
  37. ret['status'] = 10001
  38. ret['data']= "[%s] 采集 [%s] 出错了, 错误信息是:%s" % (self.hostname if self.hostname else "Agent", k, str(traceback.format_exc()))
  39. response[k] = ret
  40. return response
  41. def __cmd_run(self, cmd):
  42. if settings.MODE == 'agent':
  43. return self.__cmd_agent(cmd)
  44. elif settings.MODE == 'ssh':
  45. return self.__cmd_ssh(cmd)
  46. elif settings.MODE == 'salt':
  47. return self.__cmd_salt(cmd)
  48. else:
  49. print("只支持的模式有:agent/ssh/salt")
  50. def __cmd_agent(self, cmd):
  51. res = subprocess.getoutput(cmd)
  52. return res
  53. def __cmd_ssh(self, cmd):
  54. import paramiko
  55. # 创建SSH对象
  56. ssh = paramiko.SSHClient()
  57. # 允许连接不在know_hosts文件中的主机
  58. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  59. # 连接服务器
  60. ssh.connect(hostname=self.hostname, port=self.port, username=self.name, password=self.pwd)
  61. # 执行命令
  62. stdin, stdout, stderr = ssh.exec_command(cmd)
  63. # 获取命令结果
  64. result = stdout.read()
  65. # 关闭连接
  66. ssh.close()
  67. return result
  68. def __cmd_salt(self, cmd):
  69. command = "salt %s cmd.run %s" % (self.hostname, cmd)
  70. res = subprocess.getoutput(command)
  71. return res
  1. # basic.py
  2. class Basic(object):
  3. def __init__(self):
  4. pass
  5. @classmethod
  6. def initial(cls):
  7. return cls()
  8. def process(self, command_func, debug):
  9. if debug:
  10. output = {
  11. 'os_platform': "linux",
  12. 'os_version': "CentOS release 6.6 (Final)nKernel r on an m",
  13. 'hostname': 'c2000.com'
  14. }
  15. else:
  16. output = {
  17. 'os_platform': command_func("uname").strip(),
  18. 'os_version': command_func("cat /etc/issue").strip().split('n')[0],
  19. 'hostname': command_func("hostname").strip(),
  20. }
  21. return output
  1. # board.py
  2. import os
  3. from lib.conf.config import settings
  4. class Board(object):
  5. def __init__(self):
  6. pass
  7. @classmethod
  8. def initial(cls):
  9. return cls()
  10. def process(self, command_func, debug):
  11. if debug:
  12. output = open(os.path.join(settings.BASEDIR, 'files/board.out'), 'r', encoding='utf-8').read()
  13. else:
  14. output = command_func("sudo dmidecode -t1")
  15. return self.parse(output)
  16. def parse(self, content):
  17. result = {}
  18. key_map = {
  19. 'Manufacturer': 'manufacturer',
  20. 'Product Name': 'model',
  21. 'Serial Number': 'sn',
  22. }
  23. for item in content.split('n'):
  24. row_data = item.strip().split(':')
  25. if len(row_data) == 2:
  26. if row_data[0] in key_map:
  27. result[key_map[row_data[0]]] = row_data[1].strip() if row_data[1] else row_data[1]
  28. return result
  1. # cpu.py
  2. import os
  3. from lib.conf.config import settings
  4. class Cpu(object):
  5. def __init__(self):
  6. pass
  7. @classmethod
  8. def initial(cls):
  9. return cls()
  10. def process(self, command_func, debug):
  11. if debug:
  12. output = open(os.path.join(settings.BASEDIR, 'files/cpuinfo.out'), 'r', encoding='utf-8').read()
  13. else:
  14. output = command_func("cat /proc/cpuinfo")
  15. return self.parse(output)
  16. def parse(self, content):
  17. """
  18. 解析shell命令返回结果
  19. :param content: shell 命令结果
  20. :return:解析后的结果
  21. """
  22. response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''}
  23. cpu_physical_set = set()
  24. content = content.strip()
  25. for item in content.split('nn'):
  26. for row_line in item.split('n'):
  27. key, value = row_line.split(':')
  28. key = key.strip()
  29. if key == 'processor':
  30. response['cpu_count'] += 1
  31. elif key == 'physical id':
  32. cpu_physical_set.add(value)
  33. elif key == 'model name':
  34. if not response['cpu_model']:
  35. response['cpu_model'] = value
  36. response['cpu_physical_count'] = len(cpu_physical_set)
  37. return response
  1. #disk.py
  2. import re
  3. import os
  4. from lib.conf.config import settings
  5. class Disk(object):
  6. def __init__(self):
  7. pass
  8. @classmethod
  9. def initial(cls):
  10. return cls()
  11. def process(self, command_func, debug):
  12. if debug:
  13. output = open(os.path.join(settings.BASEDIR, 'files/disk.out'), 'r', encoding='utf-8').read()
  14. else:
  15. output = command_func("sudo MegaCli -PDList -aALL")
  16. return self.parse(output)
  17. def parse(self, content):
  18. """
  19. 解析shell命令返回结果
  20. :param content: shell 命令结果
  21. :return:解析后的结果
  22. """
  23. response = {}
  24. result = []
  25. for row_line in content.split("nnnn"):
  26. result.append(row_line)
  27. for item in result:
  28. temp_dict = {}
  29. for row in item.split('n'):
  30. if not row.strip():
  31. continue
  32. if len(row.split(':')) != 2:
  33. continue
  34. key, value = row.split(':')
  35. name = self.mega_patter_match(key)
  36. if name:
  37. if key == 'Raw Size':
  38. raw_size = re.search('(d+.d+)', value.strip())
  39. if raw_size:
  40. temp_dict[name] = raw_size.group()
  41. else:
  42. raw_size = '0'
  43. else:
  44. temp_dict[name] = value.strip()
  45. if temp_dict:
  46. response[temp_dict['slot']] = temp_dict
  47. return response
  48. @staticmethod
  49. def mega_patter_match(needle):
  50. grep_pattern = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'}
  51. for key, value in grep_pattern.items():
  52. if needle.startswith(key):
  53. return value
  54. return False
  1. # memory.py
  2. import os
  3. from lib import convert
  4. from lib.conf.config import settings
  5. class Memory(object):
  6. def __init__(self):
  7. pass
  8. @classmethod
  9. def initial(cls):
  10. return cls()
  11. def process(self, command_func, debug):
  12. if debug:
  13. output = open(os.path.join(settings.BASEDIR, 'files/memory.out'), 'r', encoding='utf-8').read()
  14. else:
  15. output = command_func("sudo dmidecode -q -t 17 2>/dev/null")
  16. return self.parse(output)
  17. def parse(self, content):
  18. """
  19. 解析shell命令返回结果
  20. :param content: shell 命令结果
  21. :return:解析后的结果
  22. """
  23. ram_dict = {}
  24. key_map = {
  25. 'Size': 'capacity',
  26. 'Locator': 'slot',
  27. 'Type': 'model',
  28. 'Speed': 'speed',
  29. 'Manufacturer': 'manufacturer',
  30. 'Serial Number': 'sn',
  31. }
  32. devices = content.split('Memory Device')
  33. for item in devices:
  34. item = item.strip()
  35. if not item:
  36. continue
  37. if item.startswith('#'):
  38. continue
  39. segment = {}
  40. lines = item.split('nt')
  41. for line in lines:
  42. if not line.strip():
  43. continue
  44. if len(line.split(':')):
  45. key, value = line.split(':')
  46. else:
  47. key = line.split(':')[0]
  48. value = ""
  49. if key in key_map:
  50. if key == 'Size':
  51. segment[key_map['Size']] = convert.convert_mb_to_gb(value, 0)
  52. else:
  53. segment[key_map[key.strip()]] = value.strip()
  54. ram_dict[segment['slot']] = segment
  55. return ram_dict
  1. #nic.py 网络接口控制器
  2. import os
  3. import re
  4. from lib.conf.config import settings
  5. class Nic(object):
  6. def __init__(self):
  7. pass
  8. @classmethod
  9. def initial(cls):
  10. return cls()
  11. def process(self, command_func, debug):
  12. if debug:
  13. output = open(os.path.join(settings.BASEDIR, 'files/nic.out'), 'r', encoding='utf-8').read()
  14. interfaces_info = self._interfaces_ip(output)
  15. else:
  16. interfaces_info = self.linux_interfaces(command_func)
  17. self.standard(interfaces_info)
  18. return interfaces_info
  19. def linux_interfaces(self, command_func):
  20. '''
  21. Obtain interface information for *NIX/BSD variants
  22. '''
  23. ifaces = dict()
  24. ip_path = 'ip'
  25. if ip_path:
  26. cmd1 = command_func('sudo {0} link show'.format(ip_path))
  27. cmd2 = command_func('sudo {0} addr show'.format(ip_path))
  28. ifaces = self._interfaces_ip(cmd1 + 'n' + cmd2)
  29. return ifaces
  30. def which(self, exe):
  31. def _is_executable_file_or_link(exe):
  32. # check for os.X_OK doesn't suffice because directory may executable
  33. return (os.access(exe, os.X_OK) and
  34. (os.path.isfile(exe) or os.path.islink(exe)))
  35. if exe:
  36. if _is_executable_file_or_link(exe):
  37. # executable in cwd or fullpath
  38. return exe
  39. # default path based on busybox's default
  40. default_path = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin'
  41. search_path = os.environ.get('PATH', default_path)
  42. path_ext = os.environ.get('PATHEXT', '.EXE')
  43. ext_list = path_ext.split(';')
  44. search_path = search_path.split(os.pathsep)
  45. if True:
  46. # Add any dirs in the default_path which are not in search_path. If
  47. # there was no PATH variable found in os.environ, then this will be
  48. # a no-op. This ensures that all dirs in the default_path are
  49. # searched, which lets salt.utils.which() work well when invoked by
  50. # salt-call running from cron (which, depending on platform, may
  51. # have a severely limited PATH).
  52. search_path.extend(
  53. [
  54. x for x in default_path.split(os.pathsep)
  55. if x not in search_path
  56. ]
  57. )
  58. for path in search_path:
  59. full_path = os.path.join(path, exe)
  60. if _is_executable_file_or_link(full_path):
  61. return full_path
  62. return None
  63. def _number_of_set_bits_to_ipv4_netmask(self, set_bits): # pylint: disable=C0103
  64. '''
  65. Returns an IPv4 netmask from the integer representation of that mask.
  66. Ex. 0xffffff00 -> '255.255.255.0'
  67. '''
  68. return self.cidr_to_ipv4_netmask(self._number_of_set_bits(set_bits))
  69. def cidr_to_ipv4_netmask(self, cidr_bits):
  70. '''
  71. Returns an IPv4 netmask
  72. '''
  73. try:
  74. cidr_bits = int(cidr_bits)
  75. if not 1 <= cidr_bits <= 32:
  76. return ''
  77. except ValueError:
  78. return ''
  79. netmask = ''
  80. for idx in range(4):
  81. if idx:
  82. netmask += '.'
  83. if cidr_bits >= 8:
  84. netmask += '255'
  85. cidr_bits -= 8
  86. else:
  87. netmask += '{0:d}'.format(256 - (2 ** (8 - cidr_bits)))
  88. cidr_bits = 0
  89. return netmask
  90. def _number_of_set_bits(self, x):
  91. '''
  92. Returns the number of bits that are set in a 32bit int
  93. '''
  94. # Taken from http://stackoverflow.com/a/4912729. Many thanks!
  95. x -= (x >> 1) & 0x55555555
  96. x = ((x >> 2) & 0x33333333) + (x & 0x33333333)
  97. x = ((x >> 4) + x) & 0x0f0f0f0f
  98. x += x >> 8
  99. x += x >> 16
  100. return x & 0x0000003f
  101. def _interfaces_ip(self, out):
  102. '''
  103. Uses ip to return a dictionary of interfaces with various information about
  104. each (up/down state, ip address, netmask, and hwaddr)
  105. '''
  106. ret = dict()
  107. right_keys = ['name', 'hwaddr', 'up', 'netmask', 'ipaddrs']
  108. def parse_network(value, cols):
  109. '''
  110. Return a tuple of ip, netmask, broadcast
  111. based on the current set of cols
  112. '''
  113. brd = None
  114. if '/' in value: # we have a CIDR in this address
  115. ip, cidr = value.split('/') # pylint: disable=C0103
  116. else:
  117. ip = value # pylint: disable=C0103
  118. cidr = 32
  119. if type_ == 'inet':
  120. mask = self.cidr_to_ipv4_netmask(int(cidr))
  121. if 'brd' in cols:
  122. brd = cols[cols.index('brd') + 1]
  123. return (ip, mask, brd)
  124. groups = re.compile('r?n\d').split(out)
  125. for group in groups:
  126. iface = None
  127. data = dict()
  128. for line in group.splitlines():
  129. if ' ' not in line:
  130. continue
  131. match = re.match(r'^d*:s+([w.-]+)(?:@)?([w.-]+)?:s+<(.+)>', line)
  132. if match:
  133. iface, parent, attrs = match.groups()
  134. if 'UP' in attrs.split(','):
  135. data['up'] = True
  136. else:
  137. data['up'] = False
  138. if parent and parent in right_keys:
  139. data[parent] = parent
  140. continue
  141. cols = line.split()
  142. if len(cols) >= 2:
  143. type_, value = tuple(cols[0:2])
  144. iflabel = cols[-1:][0]
  145. if type_ in ('inet',):
  146. if 'secondary' not in cols:
  147. ipaddr, netmask, broadcast = parse_network(value, cols)
  148. if type_ == 'inet':
  149. if 'inet' not in data:
  150. data['inet'] = list()
  151. addr_obj = dict()
  152. addr_obj['address'] = ipaddr
  153. addr_obj['netmask'] = netmask
  154. addr_obj['broadcast'] = broadcast
  155. data['inet'].append(addr_obj)
  156. else:
  157. if 'secondary' not in data:
  158. data['secondary'] = list()
  159. ip_, mask, brd = parse_network(value, cols)
  160. data['secondary'].append({
  161. 'type': type_,
  162. 'address': ip_,
  163. 'netmask': mask,
  164. 'broadcast': brd,
  165. })
  166. del ip_, mask, brd
  167. elif type_.startswith('link'):
  168. data['hwaddr'] = value
  169. if iface:
  170. if iface.startswith('pan') or iface.startswith('lo') or iface.startswith('v'):
  171. del iface, data
  172. else:
  173. ret[iface] = data
  174. del iface, data
  175. return ret
  176. def standard(self, interfaces_info):
  177. for key, value in interfaces_info.items():
  178. ipaddrs = set()
  179. netmask = set()
  180. if not 'inet' in value:
  181. value['ipaddrs'] = ''
  182. value['netmask'] = ''
  183. else:
  184. for item in value['inet']:
  185. ipaddrs.add(item['address'])
  186. netmask.add(item['netmask'])
  187. value['ipaddrs'] = '/'.join(ipaddrs)
  188. value['netmask'] = '/'.join(netmask)
  189. del value['inet']
  1. # lib/convert.py
  2. def convert_to_int(value,default=0):
  3. try:
  4. result = int(value)
  5. except Exception as e:
  6. result = default
  7. return result
  8. def convert_mb_to_gb(value,default=0):
  9. try:
  10. value = value.strip('MB')
  11. result = int(value)
  12. except Exception as e:
  13. result = default
  14. return result
  1. # bin/start.py
  2. from src.plugins import PluginsManager
  3. if __name__ == '__main__':
  4. res=PluginsManager().execute()
  5. 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使用

  1. import traceback
  2. def test():
  3. try:
  4. a = "dsadsa"
  5. int(a)
  6. except Exception as e:
  7. print(traceback.format_exc())
  8. test()

src/plugins/init.py

  1. import traceback
  2. from lib.conf.config import settings
  3. import importlib
  4. import subprocess
  5. ### 管理插件信息的类
  6. class PluginsManager(object):
  7. def __init__(self, hostname=None):
  8. self.plugins_dict = settings.PLUGINS_DICT
  9. self.hostname = hostname
  10. self.debug = settings.DEBUG
  11. if settings.MODE == 'ssh':
  12. self.port = settings.SSH_PORT
  13. self.name = settings.SSH_USERNAME
  14. self.pwd = settings.SSH_PASSWORD
  15. ### 读取配置文件中的pluginsdict, 并执行对应模块中的process方法
  16. def execute(self):
  17. response = {}
  18. for k, v in self.plugins_dict.items():
  19. ret = {"status":None, 'data':None}
  20. '''
  21. k: board,...
  22. v: src.plugins.board.Board 字符串
  23. '''
  24. try:
  25. # 1. 导入模块路径
  26. moudle_path, class_name = v.rsplit('.', 1)
  27. # 2. 导入这个路径
  28. moudle_name = importlib.import_module(moudle_path)
  29. # 3. 导入对应模块下的类
  30. classobj = getattr(moudle_name, class_name)
  31. # 4. 执行类下面对应的process方法
  32. res = classobj().process(self.__cmd_run, self.debug)
  33. ret['status'] = 10000
  34. ret['data'] = res
  35. except Exception as e:
  36. # hostname有值说明不是anget方案,是salstack或paramiko方案
  37. ret['status'] = 10001
  38. ret['data']= "[%s] 采集 [%s] 出错了, 错误信息是:%s" % (self.hostname if self.hostname else "Agent", k, str(traceback.format_exc()))
  39. response[k] = ret
  40. return response
  41. def __cmd_run(self, cmd):
  42. if settings.MODE == 'agent':
  43. return self.__cmd_agent(cmd)
  44. elif settings.MODE == 'ssh':
  45. return self.__cmd_ssh(cmd)
  46. elif settings.MODE == 'salt':
  47. return self.__cmd_salt(cmd)
  48. else:
  49. print("只支持的模式有:agent/ssh/salt")
  50. def __cmd_agent(self, cmd):
  51. res = subprocess.getoutput(cmd)
  52. return res
  53. def __cmd_ssh(self, cmd):
  54. import paramiko
  55. # 创建SSH对象
  56. ssh = paramiko.SSHClient()
  57. # 允许连接不在know_hosts文件中的主机
  58. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  59. # 连接服务器
  60. ssh.connect(hostname=self.hostname, port=self.port, username=self.name, password=self.pwd)
  61. # 执行命令
  62. stdin, stdout, stderr = ssh.exec_command(cmd)
  63. # 获取命令结果
  64. result = stdout.read()
  65. # 关闭连接
  66. ssh.close()
  67. return result
  68. def __cmd_salt(self, cmd):
  69. command = "salt %s cmd.run %s" % (self.hostname, cmd)
  70. res = subprocess.getoutput(command)
  71. return res

2.8 把采集到的数据上传

客户端

  1. ## src/client
  2. import requests
  3. from lib.conf.config import settings
  4. from src.plugins import PluginsManager
  5. import os
  6. class Base():
  7. def post_data(self, server_info):
  8. requests.post(settings.API_URL, json=server_info)
  9. class Agent(Base):
  10. ### 收集数据并发
  11. def collectAndPost(self):
  12. server_info = PluginsManager().execute()
  13. hostname = server_info['basic']['data']['hostname'] ### c10000.com
  14. res = open(os.path.join(settings.BASEDIR, 'config/cert'), 'r', encoding='utf-8').read()
  15. if not res.strip():
  16. #### 第一次采集, 将采集的hostname写入到一个文件中
  17. with open(os.path.join(settings.BASEDIR, 'config/cert'), 'w', encoding='utf-8') as fp:
  18. fp.write(hostname)
  19. else:
  20. #### 第二次采集的时候, 永远以第一次文件中保存的主机名为标准
  21. server_info['basic']['data']['hostname'] = res
  22. for k, v in server_info.items():
  23. print(k, v)
  24. # requests.post(settings.API_URL, data=json.dumps(res))
  25. ### Content-Type':"application/json"
  26. self.post_data(server_info)
  27. class SSHSalt(Base):
  28. def get_hostnames(self):
  29. hostnames = requests.get(settings.API_URL)
  30. return ['c1.com', 'c2.com']
  31. def run(self, hostname):
  32. server_info = PluginsManager(hostname).execute()
  33. self.post_data(server_info)
  34. def collectAndPost(self):
  35. hostnames = self.get_hostnames()
  36. ### 单线程执行, 循环速度比较慢
  37. # for hostname in hostnames:
  38. # server_info = PluginsManager(hostname).execute()
  39. # self.post_data(server_info)
  40. ### 线程池的方式采集数据
  41. from concurrent.futures import ThreadPoolExecutor
  42. p = ThreadPoolExecutor(10)
  43. for hostname in hostnames:
  44. p.submit(self.run, hostname)
  1. # src/script.py
  2. from src.client import Agent
  3. from src.client import SSHSalt
  4. from lib.conf.config import settings
  5. def run():
  6. if settings.MODE == 'agent':
  7. obj = Agent()
  8. else:# 不管salt和paramiko方式,都需要从服务器获取客户端ip地址
  9. obj = SSHSalt()
  10. obj.collectAndPost()
  1. # bin/start.py
  2. from src.script import run
  3. if __name__ == '__main__':
  4. run()

服务端

2.9 唯一标识的问题

  1. # 目标:将变更的信息通过程序的比对, 记录下来
  2. #第一天的时候:
  3. # 采集数据:
  4. {'status': 10000, 'data': {'os_platform': 'linux', 'os_version': 'CentOS release 6.6 (Final)nKernel r on an \m', 'hostname': 'c2.com'}}
  5. #API清洗的时候:
  6. 因为是第一次, 数据库中并没有采集的数据
  7. 数据入库:
  8. server1000
  9. id sn os_platform os_version disk_size
  10. 1 dsadsa linux CentOS 250G
  11. ........
  12. #第二天的时候(数据发生变化,应该比对):
  13. #采集数据:
  14. {'status': 10000, 'data': {'os_platform': 'linux', 'os_version': 'CentOS release 6.6 (Final)nKernel r on an \m', 'hostname': 'c2.com'}}
  15. {'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'}}}
  16. # API清洗的时候:
  17. 应该在新的POST数据中选取一个 唯一 的字段, 然后到数据库中作为where条件, 获取到对应的数据
  18. 问题是 应该选取谁?
  19. 选取的是 sn 序列号(mac地址) 作为唯一的字段
  20. sn遇到的问题:
  21. 虚拟机和实体机共用一个sn 导致数据不准确
  22. # 解决的方案:
  23. a. 如果公司不需要采集虚拟机的信息, 使用sn没有问题
  24. b. 采用 hostname 作为唯一标识
  25. - 是允许开发可以临时修改主机名的
  26. -实现方案:
  27. -1. 给这些服务器分配唯一的主机名
  28. -2 将分配好的主机名录入到后台管理的DBserver表中
  29. -3. 将采集的client客户端代码, 运行一次
  30. -4 然后将得到的主机名地址保存到一个文件中
  31. 第一天:
  32. 1. 给这些服务器分配唯一的主机名
  33. 2. 将分配好的主机名录入到后台管理的DBserver表中
  34. 3. 将采集的client客户端代码, 运行一次,
  35. 然后将得到的主机名地址保存到一个文件中
  36. 第二天:
  37. hostname = server_info['basic']['data']['hostname'] ### c10000.com
  38. res = open(os.path.join(settings.BASEDIR, 'config/cert'), 'r', encoding='utf-8').read()
  39. if not res.strip():
  40. #### 第一次采集, 将采集的hostname写入到一个文件中
  41. with open(os.path.join(settings.BASEDIR, 'config/cert'), 'w', encoding='utf-8') as fp:
  42. fp.write(hostname)
  43. else:
  44. #### 第二次采集的时候, 永远以第一次文件中保存的主机名为标准
  45. server_info['basic']['data']['hostname'] = res

代码实现

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

2.10 API的验证

第一种方式

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

第二种方式

  1. ## 客户端:
  2. import requests
  3. token = "dsabdshanbdjsanjdsanjds"
  4. import time
  5. client_time = time.time()
  6. tmp = "%s|%s" % (token, client_time)
  7. ##### 加密
  8. import hashlib
  9. m = hashlib.md5()
  10. m.update(bytes(tmp, encoding='utf8'))
  11. res = m.hexdigest()
  12. client_md5_token = "%s|%s" % (res, client_time)
  13. #### 切记, 进行token验证的时候, 一定是将token写在http的请求头中
  14. data = requests.get("http://127.0.0.1:8000/getInfo/", headers = {"token":client_md5_token})
  15. print(data.text)
  16. # 服务端:
  17. server_token = "dsabdshanbdjsanjdsanjds"
  18. server_time = time.time()
  19. client_md5_header = request.META.get('HTTP_TOKEN')
  20. client_md5_token, client_time = client_md5_header.split('|')
  21. client_time = float(client_time)
  22. if server_time - client_time > 10:
  23. return HttpResponse(' 时间太久了.....')
  24. tmp = "%s|%s" % (server_token, client_time)
  25. m = hashlib.md5()
  26. m.update(bytes(tmp, encoding='utf-8'))
  27. server_md5_token = m.hexdigest()
  28. if server_md5_token != client_md5_token:
  29. return HttpResponse('修改了token')

三 服务端编写

3.1 后台表结构

Disk表:

NIC表:

Memory表:

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

跟上面三个表是一对多

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

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

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

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

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

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

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

  1. from django.db import models
  2. class UserProfile(models.Model):
  3. """
  4. 用户信息
  5. """
  6. name = models.CharField(u'姓名', max_length=32)
  7. email = models.EmailField(u'邮箱')
  8. phone = models.CharField(u'座机', max_length=32)
  9. mobile = models.CharField(u'手机', max_length=32)
  10. password = models.CharField(u'密码', max_length=64)
  11. class Meta:
  12. verbose_name_plural = "用户表"
  13. def __str__(self):
  14. return self.name
  15. class UserGroup(models.Model):
  16. """
  17. 用户组
  18. """
  19. name = models.CharField(max_length=32, unique=True)
  20. users = models.ManyToManyField('UserProfile')
  21. class Meta:
  22. verbose_name_plural = "用户组表"
  23. def __str__(self):
  24. return self.name
  25. class BusinessUnit(models.Model):
  26. """
  27. 业务线
  28. """
  29. name = models.CharField('业务线', max_length=64, unique=True)
  30. contact = models.ForeignKey('UserGroup', verbose_name='业务联系人', related_name='c')
  31. manager = models.ForeignKey('UserGroup', verbose_name='系统管理员', related_name='m')
  32. class Meta:
  33. verbose_name_plural = "业务线表"
  34. def __str__(self):
  35. return self.name
  36. class IDC(models.Model):
  37. """
  38. 机房信息
  39. """
  40. name = models.CharField('机房', max_length=32)
  41. floor = models.IntegerField('楼层', default=1)
  42. class Meta:
  43. verbose_name_plural = "机房表"
  44. def __str__(self):
  45. return self.name
  46. class Tag(models.Model):
  47. """
  48. 资产标签
  49. """
  50. name = models.CharField('标签', max_length=32, unique=True)
  51. class Meta:
  52. verbose_name_plural = "标签表"
  53. def __str__(self):
  54. return self.name
  55. class Server(models.Model):
  56. """
  57. 服务器信息
  58. """
  59. device_type_choices = (
  60. (1, '服务器'),
  61. (2, '交换机'),
  62. (3, '防火墙'),
  63. )
  64. device_status_choices = (
  65. (1, '上架'),
  66. (2, '在线'),
  67. (3, '离线'),
  68. (4, '下架'),
  69. )
  70. device_type_id = models.IntegerField('服务器类型',choices=device_type_choices, default=1)
  71. device_status_id = models.IntegerField('服务器状态',choices=device_status_choices, default=1)
  72. cabinet_num = models.CharField('机柜号', max_length=30, null=True, blank=True)
  73. cabinet_order = models.CharField('机柜中序号', max_length=30, null=True, blank=True)
  74. idc = models.ForeignKey('IDC', verbose_name='IDC机房', null=True, blank=True)
  75. business_unit = models.ForeignKey('BusinessUnit', verbose_name='属于的业务线', null=True, blank=True)
  76. tag = models.ManyToManyField('Tag')
  77. hostname = models.CharField('主机名',max_length=128, unique=True)
  78. sn = models.CharField('SN号', max_length=64, db_index=True)
  79. manufacturer = models.CharField(verbose_name='制造商', max_length=64, null=True, blank=True)
  80. model = models.CharField('型号', max_length=64, null=True, blank=True)
  81. manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True)
  82. os_platform = models.CharField('系统', max_length=16, null=True, blank=True)
  83. os_version = models.CharField('系统版本', max_length=16, null=True, blank=True)
  84. cpu_count = models.IntegerField('CPU个数', null=True, blank=True)
  85. cpu_physical_count = models.IntegerField('CPU物理个数', null=True, blank=True)
  86. cpu_model = models.CharField('CPU型号', max_length=128, null=True, blank=True)
  87. create_at = models.DateTimeField(auto_now_add=True, blank=True)
  88. class Meta:
  89. verbose_name_plural = "服务器表"
  90. def __str__(self):
  91. return self.hostname
  92. class Disk(models.Model):
  93. """
  94. 硬盘信息
  95. """
  96. slot = models.CharField('插槽位', max_length=8)
  97. model = models.CharField('磁盘型号', max_length=32)
  98. capacity = models.CharField('磁盘容量GB', max_length=32)
  99. pd_type = models.CharField('磁盘类型', max_length=32)
  100. server_obj = models.ForeignKey('Server',related_name='disk')
  101. class Meta:
  102. verbose_name_plural = "硬盘表"
  103. def __str__(self):
  104. return self.slot
  105. class NIC(models.Model):
  106. """
  107. 网卡信息
  108. """
  109. name = models.CharField('网卡名称', max_length=128)
  110. hwaddr = models.CharField('网卡mac地址', max_length=64)
  111. netmask = models.CharField(max_length=64)
  112. ipaddrs = models.CharField('ip地址', max_length=256)
  113. up = models.BooleanField(default=False)
  114. server_obj = models.ForeignKey('Server',related_name='nic')
  115. class Meta:
  116. verbose_name_plural = "网卡表"
  117. def __str__(self):
  118. return self.name
  119. class Memory(models.Model):
  120. """
  121. 内存信息
  122. """
  123. slot = models.CharField('插槽位', max_length=32)
  124. manufacturer = models.CharField('制造商', max_length=32, null=True, blank=True)
  125. model = models.CharField('型号', max_length=64)
  126. capacity = models.FloatField('容量', null=True, blank=True)
  127. sn = models.CharField('内存SN号', max_length=64, null=True, blank=True)
  128. speed = models.CharField('速度', max_length=16, null=True, blank=True)
  129. server_obj = models.ForeignKey('Server',related_name='memory')
  130. class Meta:
  131. verbose_name_plural = "内存表"
  132. def __str__(self):
  133. return self.slot
  134. class AssetRecord(models.Model):
  135. """
  136. 资产变更记录,creator为空时,表示是资产汇报的数据。
  137. """
  138. asset_obj = models.ForeignKey('Server', related_name='ar')
  139. content = models.TextField(null=True)# 新增硬盘
  140. creator = models.ForeignKey('UserProfile', null=True, blank=True) #
  141. create_at = models.DateTimeField(auto_now_add=True)
  142. class Meta:
  143. verbose_name_plural = "资产记录表"
  144. def __str__(self):
  145. return "%s-%s-%s" % (self.asset_obj.idc.name, self.asset_obj.cabinet_num, self.asset_obj.cabinet_order)
  146. class ErrorLog(models.Model):
  147. """
  148. 错误日志,如:agent采集数据错误 或 运行错误
  149. """
  150. asset_obj = models.ForeignKey('Server', null=True, blank=True)
  151. title = models.CharField(max_length=16)
  152. content = models.TextField()
  153. create_at = models.DateTimeField(auto_now_add=True)
  154. class Meta:
  155. verbose_name_plural = "错误日志表"
  156. def __str__(self):
  157. return self.title
  158. # admin管理
  159. from repository import models
  160. admin.site.register(models.Server)
  161. admin.site.register(models.UserProfile)
  162. admin.site.register(models.UserGroup)
  163. admin.site.register(models.BusinessUnit)
  164. admin.site.register(models.IDC)
  165. admin.site.register(models.Tag)
  166. admin.site.register(models.Disk)
  167. admin.site.register(models.Memory)
  168. admin.site.register(models.NIC)
  169. admin.site.register(models.AssetRecord)
  170. admin.site.register(models.ErrorLog)
  171. # 录入信息
  172. # 录入三条业务线:互娱部,新闻部,云计算部
  173. # 录入用户组:A组,B组,C组
  174. # 录入管理员:张三,李四,王五
  175. # 录入Server数据:服务器,上架,机柜号13,机柜中序号32,IDC机房,业务线,标签,主机名(c2.com)
  176. # 录入IDC机房:世纪互联,神州
  177. # 录入标签:web,db,cache

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

  1. # 新增:new-old
  2. # 删除:old-new
  3. # 更新:交集
  4. # 差集
  5. new_slot_list={0,1,2}
  6. old_slot_list={0,1}
  7. # 差集
  8. res=new_slot_list-old_slot_list
  9. print(res)
  10. # 或者
  11. res=new_slot_list.difference(old_slot_list)
  12. print(res)
  13. # 交集
  14. print(new_slot_list & old_slot_list)
  15. # 或者
  16. print(new_slot_list.intersection(old_slot_list))
  1. def getInfo(request):
  2. if request.method == 'POST':
  3. data = request.body
  4. # print(data)
  5. data = json.loads(data)
  6. #### 通过主机名获取老的数据对应的记录
  7. hostname = data['basic']['data']['hostname']
  8. old_server_info = models.Server.objects.filter(hostname=hostname).first() ## obj
  9. if not old_server_info:
  10. return HttpResponse('资产不存在')
  11. #### 以分析disk硬盘数据为例, 进行比对分析
  12. #### 如果采集出错的话, 记录错误的信息
  13. if data['disk']['status'] != 10000:
  14. models.ErrorLog.objects.create(asset_obj=old_server_info, title = "%s 采集硬盘出错了" % (hostname), content=data['disk']['data'])
  15. '''
  16. {
  17. '0': {'slot': '0', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006 LS08S0K2B5NV'},
  18. '1': {'slot': '1', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006 LS08S0K2B5AH'},
  19. '2': {'slot': '2', 'pd_type': 'SATA', 'capacity': '476.939', 'model': 'S1SZNSAFA01085L Samsung SSD 850 PRO 512GB EXM01B6Q'},
  20. }
  21. '''
  22. new_disk_info = data['disk']['data']
  23. '''
  24. [
  25. obj(slot:0, pd_type:SAS,......),
  26. obj(slot:1, pd_type:SATA,......),
  27. ....
  28. ]
  29. '''
  30. old_disk_info = models.Disk.objects.filter(server_obj=old_server_info).all() ## []
  31. new_slot_list = list(new_disk_info.keys())
  32. old_slot_list = []
  33. for obj in old_disk_info:
  34. old_slot_list.append(obj.slot)
  35. '''
  36. new_slot_list = [0,2]
  37. old_slot_list = [0,1]
  38. 新增: new_slot_list - old_slot_list = 2
  39. 删除: old_slot_list - new_slot_list = 1
  40. 更新: 交集
  41. '''
  42. #### 增加slot
  43. add_slot_list = set(new_slot_list).difference(set(old_slot_list))
  44. if add_slot_list:
  45. record_list = []
  46. for slot in add_slot_list:
  47. # {'slot': '0', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006 LS08S0K2B5NV'}
  48. disk_res = new_disk_info[slot]
  49. tmp = "添加插槽是:{slot}, 磁盘类型是:{pd_type}, 磁盘容量是:{capacity}, 磁盘的型号:{model}".format(**disk_res)
  50. disk_res['server_obj'] = old_server_info
  51. record_list.append(tmp)
  52. models.Disk.objects.create(**disk_res)
  53. ### 将变更新的信息添加到变更记录表中
  54. record_str = ";".join(record_list)
  55. models.AssetRecord.objects.create(asset_obj=old_server_info, content=record_str)
  56. #### 删除slot
  57. del_slot_list = set(old_slot_list).difference(set(new_slot_list))
  58. if del_slot_list:
  59. record_str = "删除的槽位是:%s" % (";".join(del_slot_list))
  60. models.Disk.objects.filter(slot__in=del_slot_list, server_obj=old_server_info).delete()
  61. models.AssetRecord.objects.create(asset_obj=old_server_info, content=record_str)
  62. #### 更新硬盘数据
  63. up_solt_list = set(new_slot_list).intersection(set(old_slot_list))
  64. if up_solt_list:
  65. record_list = []
  66. for slot in up_solt_list:
  67. ## 新的:'0': {'slot': '0', 'pd_type': 'SAS', 'capacity': '500G', 'model': 'SEAGATE ST300MM0006 LS08S0K2B5NV'}
  68. new_disk_row = new_disk_info[slot]
  69. ### 老的:obj(slot:0, pd_type:SAS,.....)
  70. old_disk_row = models.Disk.objects.filter(slot=slot, server_obj=old_server_info).first()
  71. for k, new_v in new_disk_row.items():
  72. '''
  73. k: slot, pd_type, capacity,...
  74. new_v: 0 SAS 279.396,....
  75. '''
  76. ### 利用反射
  77. ### 1. 先从老的数据中心获取老的数据
  78. old_v = getattr(old_disk_row, k)
  79. ### 2. 判断老的数据和新的数据是否相同
  80. if new_v != old_v:
  81. tmp = "槽位%s, %s由原来的%s变成了%s" % (slot, k, old_v, new_v)
  82. record_list.append(tmp)
  83. ### 3. 将新的数据设置回到老的数据行对象中
  84. setattr(old_disk_row, k, new_v)
  85. ### 4. 调用save, 保存
  86. old_disk_row.save()
  87. if record_list:
  88. models.AssetRecord.objects.create(asset_obj=old_server_info, content=";".join(record_list))
  89. return HttpResponse('ok')
  90. else:
  91. ### 第一种方式的判断
  92. # if token != server_token:
  93. # return HttpResponse('token值是错误的!')
  94. ### 连接数据库获取主机名列表
  95. token = request.META.get('HTTP_TOKEN')
  96. client_md5_token, client_time = token.split('|')
  97. client_time = float(client_time)
  98. import time
  99. server_time = time.time()
  100. if server_time - client_time > 10:
  101. return HttpResponse('第一关【超时了】')
  102. server_token = "dsabdshanbdjsanjdsanjdsa"
  103. tmp = "%s|%s" % (server_token, client_time)
  104. import hashlib
  105. m = hashlib.md5()
  106. m.update(bytes(tmp, encoding='utf8'))
  107. server_md5_token = m.hexdigest()
  108. if server_md5_token != client_md5_token:
  109. return HttpResponse('第二关【数据被修改过了】')
  110. #### 第三关, 连接redis
  111. ### 第一次来的时候, 先去redis中判断, client_md5_token 是否在redis中,
  112. ### 如果在redis中, 则代表已经访问过了, return 回去
  113. ### 如果不在redis中, 则第一次访问, 添加到redis中, 并且设置过期时间 10s
  114. return HttpResponse('非常重要的数据')

3.3 前后端混合开发之layui

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

3.4 前后端混合开发之xadmin

  1. # adminx.py
  2. import xadmin
  3. from repository import models
  4. class DiskAdmin(object):
  5. list_display = ['id','slot' ,'model','capacity','pd_type','server_obj']
  6. search_fields = ['id', 'slot' ,'model','capacity','pd_type']
  7. # list_editable = ['name' ,'email','phone','mobile']
  8. # list_filter = ['name' ,'email','phone','mobile']
  9. # list_filter = ['oid','user' ,'odate','oisPay','ototal','oadress']
  10. class ServerAdmin(object):
  11. list_display = ['id', 'device_type_id', 'device_status_id', 'idc', 'business_unit', 'hostname', 'create_at']
  12. show_detail_fields = ['hostname']
  13. # search_fields = ['id', 'slot', 'model', 'capacity', 'pd_type']
  14. # data_charts = {
  15. # "user_count": {'title': u"服务器分布", "x-field": "idc", "y-field": ("business_unit",),},
  16. # # "avg_count": {'title': u"Avg Report", "x-field": "date", "y-field": ('avg_count',), "order": ('date',)}
  17. # }
  18. # list_per_page = 2
  19. data_charts = {
  20. "host_service_type_counts": {
  21. 'title': '部门机器使用情况',
  22. 'x-field': "business_unit",
  23. 'y-field': ("business_unit"),
  24. 'option': {
  25. "series": {"bars": {"align": "center", "barWidth": 0.8, "show": True}},
  26. "xaxis": {"aggregate": "count", "mode": "categories"}
  27. },
  28. },
  29. "host_idc_counts": {
  30. 'title': '机房统计',
  31. 'x-field': "idc",
  32. 'y-field': ("idc",),
  33. 'option': {
  34. "series": {"bars": {"align": "center", "barWidth": 0.3, "show": True}},
  35. "xaxis": {"aggregate": "count", "mode": "categories"}
  36. }
  37. }
  38. }
  39. class IDCAdmin(object):
  40. list_display = ['id', 'name', 'floor']
  41. show_detail_fields = ['name']
  42. # search_fields = ['id', 'slot', 'model', 'capacity', 'pd_type']
  43. xadmin.site.register(models.Disk,DiskAdmin)
  44. xadmin.site.register(models.Server,ServerAdmin)
  45. xadmin.site.register(models.IDC,IDCAdmin)

3.5 前后端分离之vue-admin

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

3.6 图表展示

Highchars

  1. https://www.highcharts.com.cn/
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  6. <title>layout 后台大布局 - Layui</title>
  7. <link rel="stylesheet" href="/static/lib/layui/css/layui.css">
  8. <script src="http://cdn.highcharts.com.cn/highcharts/highcharts.js"></script>
  9. {# <script src="/static/js/hichars.js"></script>#}
  10. </head>
  11. <body class="layui-layout-body">
  12. <div class="layui-layout layui-layout-admin">
  13. <div class="layui-header">
  14. <div class="layui-logo">layui 后台布局</div>
  15. <!-- 头部区域(可配合layui已有的水平导航) -->
  16. <ul class="layui-nav layui-layout-left">
  17. <li class="layui-nav-item"><a href="">控制台</a></li>
  18. <li class="layui-nav-item"><a href="">商品管理</a></li>
  19. <li class="layui-nav-item"><a href="">用户</a></li>
  20. <li class="layui-nav-item">
  21. <a href="javascript:;">其它系统</a>
  22. <dl class="layui-nav-child">
  23. <dd><a href="">邮件管理</a></dd>
  24. <dd><a href="">消息管理</a></dd>
  25. <dd><a href="">授权管理</a></dd>
  26. </dl>
  27. </li>
  28. </ul>
  29. <ul class="layui-nav layui-layout-right">
  30. <li class="layui-nav-item">
  31. <a href="javascript:;">
  32. <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
  33. 贤心
  34. </a>
  35. <dl class="layui-nav-child">
  36. <dd><a href="">基本资料</a></dd>
  37. <dd><a href="">安全设置</a></dd>
  38. </dl>
  39. </li>
  40. <li class="layui-nav-item"><a href="">退了</a></li>
  41. </ul>
  42. </div>
  43. <div class="layui-side layui-bg-black">
  44. <div class="layui-side-scroll">
  45. <!-- 左侧导航区域(可配合layui已有的垂直导航) -->
  46. <ul class="layui-nav layui-nav-tree" lay-filter="test">
  47. <li class="layui-nav-item layui-nav-itemed">
  48. <a class="" href="javascript:;">所有商品</a>
  49. <dl class="layui-nav-child">
  50. <dd><a href="javascript:;">列表一</a></dd>
  51. <dd><a href="javascript:;">列表二</a></dd>
  52. <dd><a href="javascript:;">列表三</a></dd>
  53. <dd><a href="">超链接</a></dd>
  54. </dl>
  55. </li>
  56. <li class="layui-nav-item">
  57. <a href="javascript:;">解决方案</a>
  58. <dl class="layui-nav-child">
  59. <dd><a href="javascript:;">列表一</a></dd>
  60. <dd><a href="javascript:;">列表二</a></dd>
  61. <dd><a href="">超链接</a></dd>
  62. </dl>
  63. </li>
  64. <li class="layui-nav-item"><a href="">云市场</a></li>
  65. <li class="layui-nav-item"><a href="">发布商品</a></li>
  66. </ul>
  67. </div>
  68. </div>
  69. <div class="layui-body">
  70. <!-- 内容主体区域 -->
  71. <div style="padding: 15px;">
  72. <div id="container" style="max-width:800px;height:400px"></div>
  73. </div>
  74. </div>
  75. <div class="layui-footer">
  76. <!-- 底部固定区域 -->
  77. © layui.com - 底部固定区域
  78. </div>
  79. </div>
  80. <script src="/static/lib/layui/layui.js"></script>
  81. <script>
  82. //JavaScript代码区域
  83. layui.use('element', function () {
  84. var element = layui.element;
  85. });
  86. var chart = Highcharts.chart('container', {
  87. title: {
  88. text: '用户活跃量'
  89. },
  90. yAxis: {
  91. title: {
  92. text: '用户人数'
  93. }
  94. },
  95. legend: {
  96. layout: 'vertical',
  97. align: 'right',
  98. verticalAlign: 'middle'
  99. },
  100. plotOptions: {
  101. series: {
  102. label: {
  103. connectorAllowed: false
  104. },
  105. pointStart: 1
  106. }
  107. },
  108. series: [{
  109. name: '用户登录系统人数',
  110. data: [10, 20, 14, 30, 55, 77, 99, 12]
  111. },],
  112. responsive: {
  113. rules: [{
  114. condition: {
  115. maxWidth: 500
  116. },
  117. chartOptions: {
  118. legend: {
  119. layout: 'horizontal',
  120. align: 'center',
  121. verticalAlign: 'bottom'
  122. }
  123. }
  124. }]
  125. }
  126. });
  127. </script>
  128. </body>
  129. </html>

echars

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="utf-8">
    5. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    6. <title>layout 后台大布局 - Layui</title>
    7. <link rel="stylesheet" href="/static/lib/layui/css/layui.css">
    8. <script src="https://cdn.jsdelivr.net/npm/echarts@5.0.2/dist/echarts.min.js"></script>
    9. </head>
    10. <body class="layui-layout-body">
    11. <div class="layui-layout layui-layout-admin">
    12. <div class="layui-header">
    13. <div class="layui-logo">layui 后台布局</div>
    14. <!-- 头部区域(可配合layui已有的水平导航) -->
    15. <ul class="layui-nav layui-layout-left">
    16. <li class="layui-nav-item"><a href="">控制台</a></li>
    17. <li class="layui-nav-item"><a href="">商品管理</a></li>
    18. <li class="layui-nav-item"><a href="">用户</a></li>
    19. <li class="layui-nav-item">
    20. <a href="javascript:;">其它系统</a>
    21. <dl class="layui-nav-child">
    22. <dd><a href="">邮件管理</a></dd>
    23. <dd><a href="">消息管理</a></dd>
    24. <dd><a href="">授权管理</a></dd>
    25. </dl>
    26. </li>
    27. </ul>
    28. <ul class="layui-nav layui-layout-right">
    29. <li class="layui-nav-item">
    30. <a href="javascript:;">
    31. <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
    32. 贤心
    33. </a>
    34. <dl class="layui-nav-child">
    35. <dd><a href="">基本资料</a></dd>
    36. <dd><a href="">安全设置</a></dd>
    37. </dl>
    38. </li>
    39. <li class="layui-nav-item"><a href="">退了</a></li>
    40. </ul>
    41. </div>
    42. <div class="layui-side layui-bg-black">
    43. <div class="layui-side-scroll">
    44. <!-- 左侧导航区域(可配合layui已有的垂直导航) -->
    45. <ul class="layui-nav layui-nav-tree" lay-filter="test">
    46. <li class="layui-nav-item layui-nav-itemed">
    47. <a class="" href="javascript:;">所有商品</a>
    48. <dl class="layui-nav-child">
    49. <dd><a href="javascript:;">列表一</a></dd>
    50. <dd><a href="javascript:;">列表二</a></dd>
    51. <dd><a href="javascript:;">列表三</a></dd>
    52. <dd><a href="">超链接</a></dd>
    53. </dl>
    54. </li>
    55. <li class="layui-nav-item">
    56. <a href="javascript:;">解决方案</a>
    57. <dl class="layui-nav-child">
    58. <dd><a href="javascript:;">列表一</a></dd>
    59. <dd><a href="javascript:;">列表二</a></dd>
    60. <dd><a href="">超链接</a></dd>
    61. </dl>
    62. </li>
    63. <li class="layui-nav-item"><a href="">云市场</a></li>
    64. <li class="layui-nav-item"><a href="">发布商品</a></li>
    65. </ul>
    66. </div>
    67. </div>
    68. <div class="layui-body">
    69. <!-- 内容主体区域 -->
    70. <div style="padding: 15px;">
    71. <div id="main" style="width: 600px;height:400px;"></div>
    72. </div>
    73. </div>
    74. <div class="layui-footer">
    75. <!-- 底部固定区域 -->
    76. © layui.com - 底部固定区域
    77. </div>
    78. </div>
    79. <script src="/static/lib/layui/layui.js"></script>
    80. <script>
    81. //JavaScript代码区域
    82. layui.use('element', function () {
    83. var element = layui.element;
    84. });
    85. var myChart = echarts.init(document.getElementById('main'));
    86. option = {
    87. xAxis: {
    88. type: 'category',
    89. data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    90. },
    91. yAxis: {
    92. type: 'value'
    93. },
    94. series: [{
    95. data: [150, 230, 224, 218, 135, 147, 260],
    96. type: 'line'
    97. }]
    98. };
    99. myChart.setOption(option);
    100. </script>
    101. </body>
    102. </html>
posted @   甜甜de微笑  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界
点击右上角即可分享
微信分享提示