一、项目架构:目录规范

# 遵循软件开发架构目录规范
bin 启动文件
src 源文件(核心代码)
config 配置文件
lib 公共方法
tests 测试文件

二、采集规范

# bin目录下新建start.py
# Autor:cxiong from lib.conf.config import settings if __name__ == '__main__':
print(settings.USER) # config下新建custom_settings.py
USER = '自定义用户配置' # lib下新建conf目录,再新建config.py和global_settings.py
# config.py
from config import custom_settings
from . import global_settings class Settings:
def __init__(self):
# 先设置默认配置,再设置自定义配置
for name in dir(global_settings):
if name.isupper():
k = name
v = getattr(global_settings, k)
setattr(self, k, v) # 自定义配置
for name in dir(custom_settings):
if name.isupper():
k = name
v = getattr(custom_settings, k)
setattr(self, k, v) settings = Settings() # global_settings.py USER = "默认用户配置" # 运行start.py后可以查看获得测试结果

插拔式功能

#直接在start.py中书写逻辑代码
# mode在settings设置
# Autor:cxiong from lib.conf.config import settings if __name__ == '__main__':
# 先读取配置文件方案配置
mode = settings.MODE
# 根据方案的不同,书写不同代码
if mode == 'agent':
import subprocess
res = subprocess.getoutput('ipconfig')
# 针对获取到的数据进行筛选处理
print(res)
elif mode == 'ssh':
import paramiko
# 创建对象
ssh = paramiko.SSHClient()
# 允许链接不在konows_hosts里的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 链接服务器
ssh.connect(hostname='127.
0.0.1',port=2222,username='root',password='123123')
# 执行命令
stdin,stdout,stderr = ssh.exec_command('ifconfig')
# 获取结果
res = stdout.read()
# 断开链接
ssh.close()
print(res)
else:
import salt.client
local = salt.client.LocalClient()
res = local.cmd('127.0.0.1','cmd.run',['ifconfig'])
print(res) # 这种方案存在的问题
存在的问题:
1.面向过程编程,扩展性差,后期不好维护、扩展
2.不符合代码设计规范
# 高内聚低耦合
"""在写函数或者类的时候,代码中尽量不要多一行与函数或者类无关的代码(按照功能的不同拆分细化成不同的代码块)"""
def get_user():
# 先获取订单数据
get_order()
# 才能获取用户数据
pass
def get_order():
pass

初始版本

# 遵循高内聚低耦合
'''
将面向过程编程修改为面向对象编程
在src文件夹内创建plugins文件夹,在该文件夹内根据信息的不同创建不同的py文件
存在的问题:
根据业务逻辑的不同,可能需要增加或者减少功能
代码需要修改,比较麻烦
3.参考django中间件
中间件如果我们不想执行某个只需要在配置文件中注释掉一行即可
如果想只需要添加一行字符串即可,并且也可以自定义中间件
'''
"""
django中间件方法例子
需求:开发一个通知系统
可以发邮件通知 短信通知 微信通知 # settings.py
NOTIFY_LIST = [
'notify.email.Email', # 类的字符串路径
'notify.message.Message',
'notify.weixin.Weixin',
'notify.qq.QQ'
] # notify/目录下
# email.py
class Email:
def __init__(self):
pass def send(self, message):
print('邮箱通知:%s' %message) # settings文件
# 模仿django配置文件功能
NOTIFY_LIST = [
'notify.email.Email', # 类的字符串路径
'notify.message.Message',
'notify.weixin.Weixin',
'notify.qq.QQ'
] # __init__.py # 主要配置方法
import settings
import importlib def send_all(message):
# 获取到所有发送通知的类,并且实例化产生对象调用send的方法
for i in settings.NOTIFY_LIST:
module_path, class_str = i.rsplit('.', maxsplit=1)
# print(module_path, class_str)
module = importlib.import_module(module_path) # from notify import email
class_name = getattr(module,class_str) # 根据字符串获取模块里的变量名
# print(class_name)
obj = class_name() # 实例化对象
obj.send(message) # 调用类里面绑定给对象的方法 """

迭代版本

上面迭代版本有插拔式模块,可以根据需求增加模块并在settings.py中添加就可以实现

"""
最终版本
多个采集py文件中出现了大量的重复代码
1.将多个类里面相同的属性或者代码抽取出来形成一个父类
"""
对象:具有一系列属性和功能的结合体
类:多个对象共同的属性和功能的结合体
父类:多个类共同的属性和功能的结合体
"""
class Base:
# 填写if代码
class Board(Base):
pass
2.在PluginsManager中定义一个方法传递给所有的对象 完善代码
1.__cmd_shh需要用户名、密码、端口等信息
2.__cmd_salt需要服务器地址
也就意味着不同的方案需要有不同的额外参数
class PluginsManager:
def __init__(self, hostname=None):
self.plugins_dict = settings.PLUGINS_DICT
self.hostname = hostname
if settings.mode == 'ssh':
self.port = settings.SSH_PORT
self.name = settings.SSH_USERNAME
self.pwd = settings.SSH_PASSWORD
"""
这里有一个前提:所有的服务器上都必须有一个相同的用户
而这个前提在实际工作中也是可以的实现的,是安全且被允许的
"""

代码:IP以及账号密码暂未处理;服务器信息采集命令就是固定的

# 服务器账号密码IP端口和命令暂未分离
# 仅分离了功能 """ bin/start.py """
from src.plugins import PluginsManager
if __name__ == '__main__':
res = PluginsManager().execute()
print(res) """ config/custom_settings.py """
# 采集方案
MODE = 'agent' # 基于django中间件思想完成功能的插拔式设计
PLUGINS_DICT = {
"board": "src.plugins.board.Board",
"disk": "src.plugins.disk.Disk",
"memory": "src.plugins.memory.Memory",
} """lib/conf/config.py"""
from config import custom_settings
from . import global_settings class Settings:
def __init__(self):
# 先设置默认配置,再设置自定义配置
for name in dir(global_settings):
if name.isupper():
k = name
v = getattr(global_settings, k)
setattr(self, k, v) # 自定义配置
for name in dir(custom_settings):
if name.isupper():
k = name
v = getattr(custom_settings, k)
setattr(self, k, v) settings = Settings() """lib/conf/global_settings.py"""
USER = "默认用户配置" """src/plugins/board.py"""
# 采集主板信息
from lib.conf.config import settings
class Board:
def process(self,command_func):
command_func('ipconfig')
return "board info" """src/plugins/__init__.py"""
from lib.conf.config import settings class PluginsManager:
def __init__(self):
self.plugins_dict = settings.PLUGINS_DICT def execute(self):
# {'board': 'src.plugins.board.Board', 'disk': 'src.plugins.disk.Disk', 'memory': 'src.plugins.memory.Memory'}
response = {}
for k,v in self.plugins_dict.items():
# k标识,v类路径
module_path,class_str = v.rsplit('.',maxsplit=1)
# 利用字符串导入模块
import importlib
module_name = importlib.import_module(module_path)
# 获取类变量名
class_name=getattr(module_name,class_str)
#类名加括号实例化对象
class_obj=class_name()
# 执行绑定方法process
res = class_obj.process(self.__cmd_run)
response[k] = res
return response # 定义一个私有的方法
def __cmd_run(self,cmd):
# 根据方案的不同,书写不同代码
mode = settings.MODE
if mode == 'agent':
self.__cmd_agent(cmd) elif mode == 'ssh':
self.__cmd_ssh(cmd)
# import paramiko
# # 创建对象
# ssh = paramiko.SSHClient()
# # 允许链接不在konows_hosts里的主机
# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# # 链接服务器
# ssh.connect(hostname='127.0.0.1', port=2222, username='root', password='123123')
# # 执行命令
# stdin, stdout, stderr = ssh.exec_command(cmd)
# # 获取结果
# res = stdout.read()
# # 断开链接
# ssh.close()
# print(res)
elif mode == 'salt':
self.__cmd_salt(cmd)
# import salt.client
# local = salt.client.LocalClient()
# res = local.cmd('127.0.0.1', 'cmd.run', [cmd])
# print(res)
else:
print('目前只支持agent/SSH/salt-stack方案') # 根据模式的不同拆分不同的方法
def __cmd_agent(self,cmd):
import subprocess
res = subprocess.getoutput(cmd)
# 针对获取到的数据进行筛选处理
return res def __cmd_ssh(self,cmd):
import paramiko
# 创建对象
ssh = paramiko.SSHClient()
# 允许链接不在konows_hosts里的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 链接服务器
ssh.connect(hostname='127.0.0.1', port=2222, username='root', password='123123')
# 执行命令
stdin, stdout, stderr = ssh.exec_command(cmd)
# 获取结果
res = stdout.read()
# 断开链接
ssh.close()
return res def __cmd_salt(self,cmd):
"""python不支持salt模块,python2才能使用"""
# import salt.client
# local = salt.client.LocalClient()
# res = local.cmd('127.0.0.1', 'cmd.run', [cmd])
# return res
"""python3使用subprocess模块代替"""
import subprocess
command = 'salt "xxxxx" cmd.run %s' %cmd
res = subprocess.getoutput(command)
return res

代码

完善代码:

代码再次改善:分离IP账号密码等

# 需要修改的代码
"""src/plugins/__init__.py"""
from lib.conf.config import settings class PluginsManager:
def __init__(self,hostname=None):
self.plugins_dict = settings.PLUGINS_DICT
self.hostname =
# 前提是所有服务器都必须有一个相同的用户,实际工作中也可以实现,是安全也被允许的
if settings.mode == 'ssh':
self.port = settings.SSH_PORT
self.name = settings.SSH_USERNAME
self.pwd = settings.SSH_PASSWORD def execute(self):
# {'board': 'src.plugins.board.Board', 'disk': 'src.plugins.disk.Disk', 'memory': 'src.plugins.memory.Memory'}
response = {}
for k,v in self.plugins_dict.items():
# k标识,v类路径
module_path,class_str = v.rsplit('.',maxsplit=1)
# 利用字符串导入模块
import importlib
module_name = importlib.import_module(module_path)
# 获取类变量名
class_name=getattr(module_name,class_str)
#类名加括号实例化对象
class_obj=class_name()
# 执行绑定方法process
res = class_obj.process(self.__cmd_run)
response[k] = res
return response # 定义一个私有的方法
def __cmd_run(self,cmd):
# 根据方案的不同,书写不同代码
mode = settings.MODE
if mode == 'agent':
self.__cmd_agent(cmd)
elif mode == 'ssh':
self.__cmd_ssh(cmd)
elif mode == 'salt':
self.__cmd_salt(cmd)
else:
print('目前只支持agent/SSH/salt-stack方案') # 根据模式的不同拆分不同的方法
def __cmd_agent(self,cmd):
import subprocess
res = subprocess.getoutput(cmd)
# 针对获取到的数据进行筛选处理
return res def __cmd_ssh(self,cmd):
import paramiko
# 创建对象
ssh = paramiko.SSHClient()
# 允许链接不在konows_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)
# 获取结果
res = stdout.read()
# 断开链接
ssh.close()
return res def __cmd_salt(self,cmd):
"""python不支持salt模块,python2才能使用"""
# import salt.client
# local = salt.client.LocalClient()
# res = local.cmd('127.0.0.1', 'cmd.run', [cmd])
# return res
"""python3使用subprocess模块代替"""
import subprocess
command = 'salt %s cmd.run %s' %(self.hostname,cmd)
res = subprocess.getoutput(command)
return res """config/custom_settings"""
# 采集方案
MODE = 'agent' # 基于django中间件思想完成功能的插拔式设计
PLUGINS_DICT = {
"board": "src.plugins.board.Board",
"disk": "src.plugins.disk.Disk",
"memory": "src.plugins.memory.Memory",
} SSH_PORT = 22
SSH_USERNAME = 'root'
SSH_PASSWORD = '123'

完善代码

三、信息采集

# 采集主板信息
class Board:
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 |grep -A8 "System Information"')
return output # 针对服务器获取到的数据进行处理大致都是一样的逻辑,对字符串进行切割处理

key_map = {
"Manufacturer": '',
"Product Name": '',
"Serial Number": '',
}
res = {}
for i in data.split('\n'):
row = i.strip().split(':')
if len(row) == 2:
# 判断列表第一个元素在不在需要的字典键中
if row[0] in key_map:
res[row[0]] = row[1].strip()
print(res)

四、采集异常处理

各类接口:https://www.juhe.cn/?bd_vid=11652700683301661916

1.status_code
响应状态码
由于在采集数据的时候可能会出现各式各样的错误,我们应该在交互数据的环境添加上响应状态码 2.异常处理
import traceback
def func():
name
try:
func()
except Exception as e:
print('打印的结果:',traceback.format_exc())
打印的结果:
Traceback (most recent call last):
File "/Users/jiboyuan/PycharmProjects/autoclient/tests/s2.py", line 7, in <module>
func()
File "/Users/jiboyuan/PycharmProjects/autoclient/tests/s2.py", line 5, in func
name
NameError: name 'name' is not defined

五、服务端数据采集

1.需要将采集到的数据发送给服务端
但是直接在start.py中书写又不符合代码编写规范 2.针对django后端的requests对象
当提交post请求获取数据都是用的requests.POST
但是只有在contentType参数是urlencoded的时候requests.POST才会有数据
如果是application/json,提交post请求数据并不会放到requests.POST中而是原封不动的放在requests.body中
requests.body中数据都是原封不动的二进制格式(bytes类型) 3.针对agent模式我们需要将数据基于网络发送给服务端
4.针对ssh和saltstack模式我们并不是需要将数据发送给服务端而是需要从服务端这里获取到我们想要采集的服务器地址
"""api接口中的视图函数需要做get请求和post请求处理"""
get请求用来给ssh和saltstack返回服务器地址列表
post请求用来给agent模式发送数据
def getInfo(requests):
if requests.method == 'POST':
server_info = json.loads(requests.body)
return HttpResponse('OK')
# 连接后台数据库获取主机名列表并返回
return ['c1.com','c2.com']
# 我们为了偷懒直接合并到一个视图函数 其实也可以拆开 都行

django代码:

# 创建项目
startapp API # 项目注册:settings.py,注销csrf
# INSTALLED_APPS中新增项目
'API', # urls.py新增,接收agent发送的数据
url(r'^getInfo/',views.getInfo) # API/views.py
from django.shortcuts import render, HttpResponse # Create your views here.
import json
def getInfo(requests):
# 数据获取
if requests.method == 'POST':
server_info = json.loads(requests.body)
for k,v in server_info.items():
print(k,v)
return HttpResponse('OK')
# 链接后台数据库获取主机名列表并返回
return ['c1.com','c2.com']

autoserver

5.1进程池与线程池

"""
python2
有进程池但是没有线程池
python3
既有进程池又有线程池
"""
# 客户端代码修改

# src/client.py
# 由于ssh和saltstack都是获取主机名,所以两者直接整合到一起
class SSHSalt(Base):
def get_hostnames(self):
hostnames = requests.get(settings.API_URL)
return hostnames
# 暂时用固定代码代替
# 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(20)
for hostname in hostnames:
p.submit(self.run,hostname)

# bin/start.py修改 

  from lib.conf.config import settings
  from src import client

  if __name__ == '__main__':
    if settings.MODE == 'agent':
      client.Agent().collectAndPost()
  else:
    client.SSHSalt().collectAndPost()

5.2 优化start.py启动文件,避免出现逻辑判断

# 针对start.py最最后的优化处理

# 新建文件src/srcipt.py
from src.client import Agent,SSHSalt
from lib.conf.config import settings def run():
if settings.MODE == 'agent':
obj= Agent()
else:
obj = SSHSalt()
obj.collectAndPost() # 启动文件bin/start.py修改
from src.srcipt import run if __name__ == '__main__':
run()

总结:

上述采集功能代码
如果是agent模式,只需要将代码部署到服务器上并执行定时任务即可
如果是ssh和saltstack方案只需要找一台服务器(中控机),通过api获取需要采集的服务器地址即可

六、唯一标识

# 在资产统计过程中要想实现数据的更新和新增依据什么字段???
原则是在新的post数据中选取一个唯一字段然后到数据库中作为wehre条件获取对应的数据
# 唯一字段
选取sn序列号(mac地址)作为唯一的字段
可能存在的问题
虚拟机和实体机是共用一个sn的,会导致数据不准确
解决的措施
1.纯业务层面上,如果公司不需要采集虚拟机信息那么使用sn没有问题(很少见)
2.采用hostname作为唯一标识
上述方案需要加认为的限制
在服务器给开发使用之前需要提前完成下列的操作
1.给这些服务器分配唯一的主机名
2.将分配好的主机名录入到后台管理的DB server表中
3.将采集的client代码运行一次,然后将得到的主机名地址保存到各自服务器某个文件中
4.之后就以该文件内主机名地址为准 """
1.针对agent模式 上述方案可以考虑使用
2.但是针对ssh和saltstack模式一旦主机名修改没有还原直接导致该服务器资产无法采集到,责任落实到修改者
"""

# src/client.py文件修改:增加主机名
class Agent(Base):
def collectAndPost(self):
server_info = PluginsManager().execute()
hostname = server_info['basic']['data']['hostname']
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 f:
f.write(hostname)
else:
# 第二次采集的时候,永远以第一次文件中保存的主机名为准
server_info['basic']['data']['hostname'] = res
# for k, v in server_info.items():
# print(k, v)
self.post_data(server_info)

唯一字段

CMDB开发(二)的更多相关文章

  1. Python CMDB开发

    Python CMDB开发   运维自动化路线: cmdb的开发需要包含三部分功能: 采集硬件数据 API 页面管理 执行流程:服务器的客户端采集硬件数据,然后将硬件信息发送到API,API负责将获取 ...

  2. iOS开发-二维码扫描和应用跳转

    iOS开发-二维码扫描和应用跳转   序言 前面我们已经调到过怎么制作二维码,在我们能够生成二维码之后,如何对二维码进行扫描呢? 在iOS7之前,大部分应用中使用的二维码扫描是第三方的扫描框架,例如Z ...

  3. javaweb学习之Servlet开发(二)

    javaweb学习总结(六)--Servlet开发(二) 一.ServletConfig讲解 1.1.配置Servlet初始化参数 在Servlet的配置文件web.xml中,可以使用一个或多个< ...

  4. Java Web高性能开发(二)

    今日要闻: 性价比是个骗局: 对某个产品学上三五天个把月,然后就要花最少的钱买最多最好的东西占最大的便宜. 感谢万能的互联网,他顺利得手,顺便享受了智商上的无上满足以及居高临下的优越感--你们一千块买 ...

  5. Android开发--二维码开发应用(转载!)

    android项目开发 二维码扫描   基于android平台的二维码扫描项目,可以查看结果并且链接网址 工具/原料 zxing eclipse 方法/步骤   首先需要用到google提供的zxin ...

  6. Android Camera系列开发 (二)通过Intent录制视频

    Android Camera系列开发 (二)通过Intent录制视频 作者:雨水  2013-8-18 CSDN博客:http://blog.csdn.net/gobitan/ 概述 使用Camera ...

  7. C#的百度地图开发(二)转换JSON数据为相应的类

    原文:C#的百度地图开发(二)转换JSON数据为相应的类 在<C#的百度地图开发(一)发起HTTP请求>一文中我们向百度提供的API的URL发起请求,并得到了返回的结果,结果是一串JSON ...

  8. Qt计算器开发(二):信号槽实现数学表达式合法性检查

    表达式的合法性 由于我们的计算器不是单步计算的,所以我们能够一次性输入一个长表达式.然而假设用户输入的长表达式不合法的话,那么就会引发灾难.所以有必要对于用户的输入做一个限制. 一些限制举例: 比方, ...

  9. (Java)微信之个人公众账号开发(二)——接收并处理用户消息(下)

    接下来,我们再讲一下图文消息: 如图: 大家可以先从开发者文档中了解一下图文消息的一些参数: 如上图,用户回复4时,ipastor返回了几条图文消息,上图中属于多图文消息,当然还有单图文消息,图文消息 ...

  10. 以太坊开发(二)使用Ganache CLI在私有链上搭建智能合约

    以太坊开发(二)使用Ganache CLI在私有链上搭建智能合约 在上一篇文章中,我们使用Truffle自带的客户端Truffle Develop,在私有链上搭建并运行了官方提供的WebPack智能合 ...

随机推荐

  1. Java事件侦听器学习记录

    前言 我们监听事件之前要有事件源source,创建事件源(Event),发布事件(publishEvent),然后才能到监听事件. 事件驱动机制是观察者模式(称发布订阅)具体实现,事件对象(Event ...

  2. KingbaseES 数据库中不同user的视图访问授权

    前言 本文的目的是实现u1用户访问ud用户下的视图权限. 测试 登录system用户并创建schema,user,并授权schema的有关权限给ud用户 TEST=# select current_u ...

  3. KingbaseES 函数与存储过程内容加密

    说明: 数据库系统使用过程中,有些业务功能在特殊的安全级别情况下,需要对数据库中的函数和存储过程进行加密存储,以保证数据库函数和过程的代码安全性.KingbaseES 数据库,提供了DBMS_DDL扩 ...

  4. Atcoder DP contest 题解

    动态规划(Atcoder DP 26题) on Atcoder on Luogu 本文同步发表于知乎专栏. Frog 1 $N$ 个石头,编号为 $1,2,...,N$.对于每个 $i(1 \leq ...

  5. Scala 复杂分词求和(二元组)

    1 package chapter07 2 3 object Test18_ComplexWordCount { 4 def main(args: Array[String]): Unit = { 5 ...

  6. #倍增,floyd#CF147B Smile House

    题目 求一张有向图的最小正环(环上结点数最小) 分析 有环当且仅当 \(f[i][i]\) 为正数, 那么考虑跑 \(n\) 次 floyd 直接转移,时间复杂度为 \(O(n^4)\) 然而没必要这 ...

  7. #模拟#洛谷 5957 [POI2017]Flappy Bird

    题目 分析 小鸟所在坐标的奇偶性一定相同, 考虑每次维护一个可行区间表示小鸟在当前列可以进入的纵坐标区间, 那么它有\(x_i-x_{i-1}\)的纵坐标最大改变差,然后根据奇偶性以及限制区间缩小范围 ...

  8. 使用脚本整合指定文件/文件夹,执行定制化 ESLint 命令

    背景 最近面对一个庞大的项目,但是只需要修改某个模块,每次都手搓命令太麻烦了,于是就想着能不能写个脚本来辅助处理这些事情. 解决方案 定制化一键 ESLint,执行文件下载地址: https://gi ...

  9. 稀疏镜像在OpenHarmony上的应用

    一.稀疏镜像升级背景 常用系统镜像格式为原始镜像,即RAW格式.镜像体积比较大,在烧录固件或者升级固件时比较耗时,而且在移动设备升级过程时比较耗费流量.为此,将原始镜像用稀疏描述,可以大大地缩减镜像体 ...

  10. HarmonyOS系统级推送服务,打造消息通知新体验

    8月4日,第五届华为开发者大会 2023(HDC.Together)再次启航.在本次大会上,华为为广大用户带来了HarmonyOS 4全新升级的体验,同时,针对HarmonyOS应用的开发,此次也全面 ...