目录结构设计
采集客户端
- bin : 可执行文件 start.py / run.py
- conf: 配置文件目录 config.py
- lib : 第三方文件目录
- src /core : 核心的源代码文件目录
//- log: 记录日志 放在 /var/logs/ 下面 - test: 测试文件目录
高级配置文件的设置
用户自定义配置
全局配置
conf.py
from conf import config ### 导入自定制配置
from . import global_settings ### 导入高级配置
class mySettings():
### 集成用户自定制的配置和默认配置
def __init__(self):
### 全局配置
for k in dir(global_settings):
if k.isupper():
v = getattr(global_settings, k)
setattr(self, k, v) #### USER = 'qqq'
### 集成用户自定义的配置
for k in dir(config):
if k.isupper():
v = getattr(config, k)
setattr(self,k, v) ### USER = root
settings = mySettings()
高内聚低耦合思想
目标:实现两套方案切换采集
可插拔式采集
两套方案实现采集主机名的思路:
第一个版本代码:
if settings.MODE == 'agent':
import subprocess
res = subprocess.getoutput('hostname')
else:
import paramiko
# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='192.168.79.131', port=22, username='root', password='root')
# 执行命令
stdin, stdout, stderr = ssh.exec_command('hostname')
# 获取命令结果
result = stdout.read()
print(result)
# 关闭连接
ssh.close()
如果上述代码这样写的话,会带来如下问题:
- 耦合度太高
- 结构混乱,导致查找问题的时候不方便
- 业务逻辑代码不能写在启动文件中
问:如何解决上述存在的问题?
答:将每一个功能都封装成一个文件,比如说采集磁盘的信息,可以搞一个disk.py文件,这个文件中所有的代码都是要和采集磁盘相关的,不能有其他 的相关代码。以此类推,采集CPU的信息,也要搞一个cpu.py文件. 这种思想就是高内聚低耦合思想
第二个版本,执行采集:
from src.plugins.basic import Basic
from src.plugins.disk import Disk
#from src.plugins.memory import Memory
if __name__ == '__main__':
Basic().process()
Disk().process()
#Memory().process()
但是上述做法不是特别的完美,解决的方案是:将这些采集的插件写到配置文件中统一管理,参考django的中间件, 可插拔式的采集
核心的采集方法:
src/plugins/_init_.py
from lib.config.conf import settings
import importlib
class PluginsManager():
def __init__(self):
self.plugins_dict = settings.PLUGINS_DICT
self.debug = settings.DEBUG
### 管理配置文件插件,采集数据 从配置文件中读取配置,循环导入模块,实例化类,执行插件类对应的采集方法
def execute(self):
### 1. 从配置文件中读取配置
response = {}
for k, v in self.plugins_dict.items():
'''
k: basic
v: src.plugins.basic.Basic
'''
### 2. 循环导入模块
'''
moudle_path : 'src.plugins.basic'
class_name : 'Basic'
'''
moudle_path, class_name = v.rsplit('.', 1)
m = importlib.import_module(moudle_path) ### 导入字符串路径的
### 3.导入类,实例化类,执行防范
cls = getattr(m, class_name)
ret = cls().process(self.command_func, self.debug )
response[k] = ret
return response
def command_func(self, cmd):
if settings.MODE == 'agent':
import subprocess
res = subprocess.getoutput(cmd)
return res
else:
import paramiko
# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='192.168.79.131', port=22, username='root', password='root')
# 执行命令
stdin, stdout, stderr = ssh.exec_command(cmd)
# 获取命令结果
result = stdout.read()
# 关闭连接
ssh.close()
return (result)
插件代码,判断方案冗余,解决方案:
1.继承 每一个子类都要继承父类的方法,通过传参的方式执行不一样的命令
2.将函数名当成一个参数传给另一个函数
插件采集核心代码
disk.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import re
import os
from lib.config.conf 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(" MegaCli -PDList -aALL") ### radi 卡 磁盘阵列
return self.parse(output)
def parse(self, content):
"""
解析shell命令返回结果
:param content: shell 命令结果
:return:解析后的结果
"""
response = {}
result = []
for row_line in content.split("
"):
result.append(row_line)
for item in result:
temp_dict = {}
for row in item.split('
'):
if not row.strip():
continue
if len(row.split(':')) != 2:
continue
key, value = row.split(':')
name = self.mega_patter_match(key)
if name:
if key == 'Raw Size':
raw_size = re.search('(d+.d+)', value.strip())
if raw_size:
temp_dict[name] = raw_size.group()
else:
raw_size = '0'
else:
temp_dict[name] = value.strip()
if temp_dict:
response[temp_dict['slot']] = temp_dict
return response
@staticmethod
def mega_patter_match(needle):
grep_pattern = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'}
for key, value in grep_pattern.items():
if needle.startswith(key):
return value
return False
board.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
from lib.config.conf import settings
class Board(object):
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(" dmidecode -t1")
return self.parse(output)
def parse(self, content):
result = {}
key_map = {
'Manufacturer': 'manufacturer',
'Product Name': 'model',
'Serial Number': 'sn',
}
for item in content.split('
'):
row_data = item.strip().split(':')
if len(row_data) == 2:
if row_data[0] in key_map:
result[key_map[row_data[0]]] = row_data[1].strip() if row_data[1] else row_data[1]
return result