一、项目架构:目录规范

# 遵循软件开发架构目录规范
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. 浅谈 KingbaseES 和 SQLServer 中的 instead of 触发器

    本文基于Kingbase和SqlServer的INSTEAD OF 触发器主要功能特点进行对比浅析,同时针对SqlServer 的INSTEAD OF 触发器提出了多种kingbase环境的等价代码方 ...

  2. 【Java】try {}里有一个 return 语句,那么紧跟在这个 try 后的 finally {}里的 code 会不会被执行,什么时候被执行,在 return 前还是后?

    try {}里有一个 return 语句,那么紧跟在这个 try 后的 finally {}里的 code 会不会被执行,什么时候被执行,在 return 前还是后? package com.test ...

  3. Python爬取腾讯视频电影名称和链接(一)

    1 import requests 2 import json 3 from bs4 import BeautifulSoup #网页解析获取数据 4 import sys 5 import re 6 ...

  4. #K-D Tree#洛谷 2093 [国家集训队]JZPFAR

    题目 平面上有 \(n\) 个点.现在有 \(m\) 次询问,每次给定一个点 \((px, py)\) 和一个整数 \(k\), 输出 \(n\) 个点中离 \((px, py)\) 的距离第 \(k ...

  5. C++ 字符串完全指南:学习基础知识到掌握高级应用技巧

    C++ 字符串 字符串用于存储文本. 一个字符串变量包含由双引号括起来的一组字符: 示例 创建一个 string 类型的变量并为其赋值: string greeting = "Hello&q ...

  6. 中间件之Mycat

    一.概念 介绍 Mycat是开源的.活跃的.基于Java语言编写的MySQL数据库中间件.可以像使用mysql一样来使用mycat,对于开发人员来说根本感觉不到mycat的存在 Mycat不负责存储数 ...

  7. k8s之构建Mysql和Wordpress集群

    一.实验目的 基于Kubernetes集群实现多负载的WordPress应用.将WordPress数据存储在后端Mysql,Mysql实现主从复制读写分离功能. 工作负载 服务 持久卷 Mysql S ...

  8. 重新整理.net core 计1400篇[九] (.net core 中的依赖注入的服务注入)

    前言 在该系列六中介绍了一个简单的依赖注入,该节介绍.net core 中的依赖注入的服务注入. ServiceDescriptor ServiceDescriptor 是服务描述的意思,这个是做什么 ...

  9. Vue Mixin 的深入浅出

    mixin, 意为混入. 比如去买冰激凌,我先要一点奶油的,再来点香草的.我就可以吃一个奶油香草的冰激凌.如果再加点草莓,我可以同时吃三个口味的冰激凌. 代码表示 假设把你已有的奶油味的称为 base ...

  10. 力扣1(java&python)-两数之和(简单)

    题目: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是, ...