handler(self):
View Code
实际应用实例(以cmdb 资产采集为例)
功能概述:资产采集分为ssh /salt /agent 三种模式,每种模式对应三种应用模块,每个应用类中都是固定的两个方法cmd和handler,cmd 实现salt/ssh 的远程登录功能,handler 实现主机列表信息获取,调用资产采集模块采集资产信息,发送数据到api。
模块类的继承关系: AgentHandler继承BaseHandler,SaltHandler、SSHHandler通过SaltAndSSHHandler间接继承BaseHandler,BaseHandler 下的cmd handler对它们进行约束;
SaltHandler和SSHHandler 下的handler 函数具有相同的功能及代码,所以handler 函数写在 SaltAndSSHHandler 类下供它们继承,为了能让它们继承Basehandler 类下的约束,SaltAndSSHHandler 继承Basehandler
src/engine/base.py
#!/usr/bin/python
# -*- coding:utf-8 -*-
import json
import requests
from config import settings
from ..plugins import get_server_info
class BaseHandler(object):
def __init__(self):
self.asset_api = settings.ASSET_API #获取配置文件中资产入库接口地址,供继承BaseHandler 的类调用
def cmd(self,command, hostname=None):
#约束所有的派生类都必须实现handler方法
raise NotImplementedError('cmd must be implemented')
def handler(self):
约束所有的派生类都必须实现handler方法
:return:
raise NotImplementedError('handler must be implemented')
class SaltAndSSHHandler(BaseHandler):
def handler(self):
处理SSH/SALT模式下的资产采集
说明:由于ssh或salt 模式都是先获取主机列表,在开启线程池批量远程获取数据,具有相同的方法,
所以在SaltAndSSHHandler 基类中定义handler 方法供他们继承
:return:
#引入线程池
from concurrent.futures import ThreadPoolExecutor
# 1. 获取未采集的主机的列表
r1 = requests.get(url=self.asset_api)
hostname_list = r1.json()
#2、实例一个20个线程的线程池对象,循环主机列表,每次20线程同时执行资产采集发送任务。
pool = ThreadPoolExecutor(20)
for hostname in hostname_list:
pool.submit(self.task, hostname)
def task(self, hostname):
资产采集,数据发送
说明:由于开启了线程池,资产采集的操作必须以函数形式放在for 循环中以线程池的方式批量同时执行。
:param hostname:
:return:
info = get_server_info(self, hostname)
# 2. 发送到api
r1 = requests.post(
url=self.asset_api,
data=json.dumps(info).encode('utf-8'),
headers={
'Content-Type': 'application/json'
print(r1)
View Code
src/engine/agent.py
#!/usr/bin/python
# -*- coding:utf-8 -*-
import json
import requests
from .base import BaseHandler
from ..plugins import get_server_info
class AgentHandler(BaseHandler):
def cmd(self,command,hostname=None):
import subprocess
return subprocess.getoutput(command)
def handler(self):
处理Agent模式下的资产采集:网卡、内存、硬盘,并发送到接口
:return:
# 1. 通过调用get_server_info获取所有的资产信息:网卡、内存、硬盘
info = get_server_info(self)
# 2. 发送到api
r1 = requests.post(
url=self.asset_api,
data=json.dumps(info).encode('utf-8'),
headers={
'Content-Type':'application/json'
print(r1)
View Code
src/engine/ssh.py
#!/usr/bin/python
# -*- coding:utf-8 -*-
from config import settings
from .base import SaltAndSSHHandler
class SSHHandler(SaltAndSSHHandler):
def cmd(self, command, hostname=None):
调用paramiko远程连接主机并执行命令,依赖rsa
:param hostname:主机名
:param command: 要执行的命令
:return:
import paramiko
private_key = paramiko.RSAKey.from_private_key_file(settings.SSH_PRIVATE_KEY)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=hostname, port=settings.SSH_PORT, username=settings.SSH_USER, pkey=private_key)
stdin, stdout, stderr = ssh.exec_command(command)
result = stdout.read()
ssh.close()
return result
View Code
src/engine/salt.py
#!/usr/bin/python
# -*- coding:utf-8 -*-
from .base import SaltAndSSHHandler
class SaltHandler(SaltAndSSHHandler):
def cmd(self, command, hostname=None):
调用saltstack远程连接主机并执行命令(saltstack的master)
:param hostname:主机名
:param command: 要执行的命令
:return:
import salt.client
local = salt.client.LocalClient()
result = local.cmd(hostname, 'cmd.run', [command])
return result[hostname]
View Code
python 项目架构设计和类的使用总结
相同属性的功能模块放在相同目录(资产采集插件disk.py memory.py 都在放在plugins目录下)
每一个功能都放在一个单独的py文件中定义一个对应的类,而不是这些类都写在同一个py文件中。这样做的好处:1、便于维护,如果哪个模块删除修改单独进行不影响整体 2、每个都是独立的一个py 文件,这样可以对这些功能模块在配置文件中以字典的形式注册,此为可插拔可扩展模式。
不同的功能定义在各自的模块(py文件)下定义自己的类,每个功能类中要实现的功能通过定义不同的方法(函数)来分步骤实现。
各个功能类中每个功能类中相同属性的方法定义成相同的函数名称(AgentHandler、SSHHandler、类中都定义了handler、cmd 函数),有时候是必须名称一致。
由于不同条件下调用不同的功能类,根据配置文件配置而导入不同的类,此时类的引入为变量模式,要使用类中的方法时(cls.handler)此时方法名称必须一致。
上述请况不仅需要方法名成相同,而且每个类中必须存在handler 类,所以此处采用继承约束,定义一个基类供这些类继承,基类中定义handler 方法,调用此方法时抛出异常(提示派生类必须定义此方法,此为约束)
各个功能类中的方法不仅功能属性相同而且代码也一致,此时可在基类中定义此方法,供这些类来继承,这样既简化代码也更有逻辑性。
A B C 三个类都有相同的方法且代码相同,可定义一个基类D定义这个方法即可,它们功能继承。于此同时A B 两个类也有另一个方法相同代码,此时定义一个基类E ,E中定义这个方法,那么AB 怎么能够同时继承E D 中的方法呢,此时A B 继承E,E继承D,AB 既继承了E 中的方法,也通过E 间接继承了D 中的方法。
多个模块中的类需要引入相同的模块中的变量,可以在它们继承的基类中定义 def __init__(self): self.asset_api = settings.ASSET_API ,然后再基类中通过self.asset_api 引入