CMDB资产采集
CMDB资产采集 |
一、概述
CMDB --Configuration Management Database 配置管理数据库, CMDB存储与管理企业IT架构中设备的各种配置信息,它与所有服务支持和服务交付流程都紧密相联,支持这些流程的运转、发挥配置信息的价值,同时依赖于相关流程保证数据的准确性。
二、资产采集方式
1.Agent方式
需要在每台服务器上安装Agent,然后每台服务器定时自动去获取信息,发送带数据库,然后后台获取数据进行处理,不过一般我们不会直接将数据直接传递到数据库,会将数据传递到API接口先进行处理,过滤,然后才会发送到数据库。具体流程如下图所示:
采集方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#放置在Agent机器上 import subprocess import requests ######################采集数据################ result = subprocess.getoutput('ipconfig') #result正则表达式处理获取想要的数据 #整理资产信息 data_dict = { } #####################发送数据################# requests.post('',data=data_dict) #第一参数为API的url,第二需要发送的数据
2.SSH方式
SSH方式需要使用paramiko模块,通过中控机服务器统一去获取指定服务器的信息。具体流程如下图所示:
采集方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#放置在资产采集机器上 #基于paramiko模块,pip3 install paramiko import requests import paramiko ######################获取今日未采集主机名################ result = requests.get('') ######################通过paramiko连接远程服务器,执行命令################ #创建SSH对象 ssh = paramiko.SSHClient() #允许连接不在know_hosts文件中的主机 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #连接服务器 ssh.connect(hostname='',port=22,username='yuanfang',password='123') #执行命令 stdin, stdout, stderr = ssh.exec_command('ipconfig') #获取命令结果 result = stdout.read() #关闭连接 ssh.close() #整理资产信息,把命令结果格式化放置data_dict data_dict = { } #####################发送数据################# requests.post('',data=data_dict) #第一参数为API的url,第二需要发送的数据
3.saltstack方式
使用master对slave进行操作,类似于列队实现。具体流程如下图所示:
采集方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#1.安装saltstack # sudo rpm --import https://repo.saltstack.com/py3/redhat/7/x86_64/archive/2019.2.2/SALTSTACK-GPG-KEY.pub # Master:sudo yum install salt-master # Slave: sudo yum install salt-minion """ Master准备: a.配置文件,监听本机IP vim /etc/salt/master interface: 本机IP地址 b.启动master /etc/init.d/salt-master start Slave准备 a.配置文件,链接哪个master vim /etc/salt/minion interface: 远程master地址 b.启动slave /etc/init.d/salt-minion start 2.创建关系 查看 Master:salt-key -L Accepted Keys: #(接受的) Denied Keys: #(拒绝的) Unaccepted Keys: #(未处理的) Rejected Keys: #(拒绝的) 接受 Master:salt-key -a 需要接受的主机名 3.执行命令 master: salt '主机名' cmd.run '命令' master进入python: from salt import client local = client.LocalClient() result = local.cmd('主机名','cmd.run',['命令']) #result是一个字典,key为主机名,value为执行结果 """ ######################获取今日未采集主机名################ # import requests # result = requests.get('') ######################远程服务器执行命令################ #方法一 # import subprocess # result = subprocess.getoutput(salt '主机名' cmd.run '命令') #方法二 # from salt import client # local = client.LocalClient() # result = local.cmd('主机名','cmd.run',['命令']) #整理资产信息,把命令结果格式化放置data_dict data_dict = { } #####################发送数据################# #requests.post('',data=data_dict)
4.puppet方式
puppet方式使用ruby写的,了解即可。具体流程如下图所示:
三、资产采集开发
1.bin目录(存放启动文件)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os import sys BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASEDIR) from src.scripts import client if __name__ == '__main__': client()
2.config目录(存放配置文件)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # -*- coding:utf-8 -*- import os BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 用于API认证的KEY KEY = '299095cc-1330-11e5-b06a-a45e60bec08b' # 用于API认证的请求头 AUTH_KEY_NAME = 'auth-key' # 错误日志 ERROR_LOG_FILE = os.path.join(BASEDIR, "log", 'error.log') # 运行日志 RUN_LOG_FILE = os.path.join(BASEDIR, "log", 'run.log') # Agent模式保存服务器唯一ID的文件 CERT_FILE_PATH = os.path.join(BASEDIR, 'config', 'cert') # 是否测试模式,测试模时候数据从files目录下读取 TEST_MODE = True # 采集资产的方式,选项有:agent(默认), salt, ssh MODE = 'ssh' # 如果采用SSH方式,则需要配置SSH的KEY和USER SSH_PRIVATE_KEY = "/home/auto/.ssh/id_rsa" SSH_USER = "root" SSH_PORT = 22 # 采集硬件数据的插件 PLUGINS_DICT = { 'cpu': 'src.plugins.cpu.CpuPlugin', 'disk': 'src.plugins.disk.DiskPlugin', 'main_board': 'src.plugins.main_board.MainBoardPlugin', 'memory': 'src.plugins.memory.MemoryPlugin', 'nic': 'src.plugins.nic.NicPlugin', } # 资产信息API ASSET_API = "http://127.0.0.1:8000/api/asset" """ POST时,返回值:{'code': xx, 'message': 'xx'} code: - 1000 成功; - 1001 接口授权失败; - 1002 数据库中资产不存在 """
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#含有主机名信息,作为CMDB项目采集资产数据唯一标识规定
3.lib目录(存放自定义文件)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#定义一种数据格式用于在客户端传递 class BaseResponse(object): def __init__(self): self.status = True self.message = None self.data = None self.error = None
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#自定义序列化 import json as default_json from json.encoder import JSONEncoder from .response import BaseResponse class JsonEncoder(JSONEncoder): def default(self, o): if isinstance(o, BaseResponse): return o.__dict__ return JSONEncoder.default(self, o) class Json(object): @staticmethod def dumps(response, ensure_ascii=True): return default_json.dumps(response, ensure_ascii=ensure_ascii, cls=JsonEncoder)
4.src目录(存放业务逻辑文件)
(1)plugins目录
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#关于CPU信息 import os import traceback from .base import BasePlugin from lib.response import BaseResponse class CpuPlugin(BasePlugin): def linux(self): response = BaseResponse() try: if self.test_mode: from config.settings import BASEDIR output = open(os.path.join(BASEDIR, 'files/cpuinfo.out'), 'r').read() else: shell_command = "cat /proc/cpuinfo" output = self.exec_shell_cmd(shell_command) response.data = self.parse(output) except Exception as e: msg = "%s linux cpu plugin error: %s" self.logger.log(msg % (self.hostname, traceback.format_exc()), False) response.status = False response.error = msg % (self.hostname, traceback.format_exc()) return response @staticmethod def parse(content): """ 解析shell命令返回结果 :param content: shell 命令结果 :return:解析后的结果 """ response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''} cpu_physical_set = set() content = content.strip() for item in content.split('\n\n'): for row_line in item.split('\n'): key, value = row_line.split(':') key = key.strip() if key == 'processor': response['cpu_count'] += 1 elif key == 'physical id': cpu_physical_set.add(value) elif key == 'model name': if not response['cpu_model']: response['cpu_model'] = value response['cpu_physical_count'] = len(cpu_physical_set) return response
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#关于基本信息 import traceback from .base import BasePlugin from lib.response import BaseResponse class BasicPlugin(BasePlugin): def os_platform(self): """ 获取系统平台 :return: """ if self.test_mode: output = 'linux' else: output = self.exec_shell_cmd('uname') return output.strip() def os_version(self): """ 获取系统版本 :return: """ if self.test_mode: output = """CentOS release 6.6 (Final)\nKernel \r on an \m""" else: output = self.exec_shell_cmd('cat /etc/issue') result = output.strip().split('\n')[0] return result def os_hostname(self): """ 获取主机名 :return: """ if self.test_mode: output = 'c1.com' else: output = self.exec_shell_cmd('hostname') return output.strip() def linux(self): response = BaseResponse() try: ret = { 'os_platform': self.os_platform(), 'os_version': self.os_version(), 'hostname': self.os_hostname(), } response.data = ret except Exception as e: msg = "%s BasicPlugin Error:%s" self.logger.log(msg % (self.hostname, traceback.format_exc()), False) response.status = False response.error = msg % (self.hostname, traceback.format_exc()) return response
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from src.plugins.basic import BasicPlugin from config import settings import importlib def get_server_info(hostname=None): """ 获取服务器基本信息 :param hostname: agent模式时,hostname为空;salt或ssh模式时,hostname表示要连接的远程服务器 :return: """ response = BasicPlugin(hostname).execute() if not response.status: return response for k, v in settings.PLUGINS_DICT.items(): module_path, cls_name = v.rsplit('.', 1) cls = getattr(importlib.import_module(module_path), cls_name) obj = cls(hostname).execute() response.data[k] = obj return response if __name__ == '__main__': ret = get_server_info() print(ret.__dict__)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#定义基类,实现部分方法 from lib.log import Logger from config import settings class BasePlugin(object): def __init__(self, hostname=''): self.logger = Logger() self.test_mode = settings.TEST_MODE self.mode_list = ['agent', 'salt', 'ssh'] if hasattr(settings, 'MODE'): self.mode = settings.MODE else: self.mode = 'agent' self.hostname = hostname def salt(self, cmd, ): import salt.client local = salt.client.LocalClient() result = local.cmd(self.hostname, 'cmd.run', [cmd]) return result[self.hostname] def ssh(self, cmd): 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=self.hostname, port=settings.SSH_PORT, username=settings.SSH_USER, pkey=private_key) stdin, stdout, stderr = ssh.exec_command(cmd) result = stdout.read() ssh.close() return result def agent(self, cmd): import subprocess output = subprocess.getoutput(cmd) return output def exec_shell_cmd(self, cmd): if self.mode not in self.mode_list: raise Exception("settings.mode must be one of ['agent', 'salt', 'ssh']") func = getattr(self, self.mode) output = func(cmd) return output def execute(self): return self.linux() def linux(self): raise Exception('You must implement linux method.')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#关于硬盘信息 import os import re import traceback from .base import BasePlugin from lib.response import BaseResponse class DiskPlugin(BasePlugin): def linux(self): response = BaseResponse() try: if self.test_mode: from config.settings import BASEDIR output = open(os.path.join(BASEDIR, 'files/disk.out'), 'r').read() else: shell_command = "sudo MegaCli -PDList -aALL" output = self.exec_shell_cmd(shell_command) response.data = self.parse(output) except Exception as e: msg = "%s linux disk plugin error: %s" self.logger.log(msg % (self.hostname, traceback.format_exc()), False) response.status = False response.error = msg % (self.hostname, traceback.format_exc()) return response def parse(self, content): """ 解析shell命令返回结果 :param content: shell 命令结果 :return:解析后的结果 """ response = {} result = [] for row_line in content.split("\n\n\n\n"): result.append(row_line) for item in result: temp_dict = {} for row in item.split('\n'): 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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#关于主板信息 import os import traceback from .base import BasePlugin from lib.response import BaseResponse class MainBoardPlugin(BasePlugin): def linux(self): response = BaseResponse() try: if self.test_mode: from config.settings import BASEDIR output = open(os.path.join(BASEDIR, 'files/board.out'), 'r').read() else: shell_command = "sudo dmidecode -t1" output = self.exec_shell_cmd(shell_command) response.data = self.parse(output) except Exception as e: msg = "%s linux mainboard plugin error: %s" self.logger.log(msg %(self.hostname, traceback.format_exc()), False) response.status = False response.error = msg %(self.hostname, traceback.format_exc()) return response def parse(self, content): result = {} key_map = { 'Manufacturer': 'manufacturer', 'Product Name': 'model', 'Serial Number': 'sn', } for item in content.split('\n'): 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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#关于内存信息 import os import traceback from lib import convert from .base import BasePlugin from lib.response import BaseResponse class MemoryPlugin(BasePlugin): def linux(self): response = BaseResponse() try: if self.test_mode: from config.settings import BASEDIR output = open(os.path.join(BASEDIR, 'files/memory.out'), 'r').read() else: shell_command = "sudo dmidecode -q -t 17 2>/dev/null" output = self.exec_shell_cmd(shell_command) response.data = self.parse(output) except Exception as e: msg = "%s linux memory plugin error: %s" self.logger.log(msg % (self.hostname, traceback.format_exc()), False) response.status = False response.error = msg % (self.hostname, traceback.format_exc()) return response def parse(self, content): """ 解析shell命令返回结果 :param content: shell 命令结果 :return:解析后的结果 """ ram_dict = {} key_map = { 'Size': 'capacity', 'Locator': 'slot', 'Type': 'model', 'Speed': 'speed', 'Manufacturer': 'manufacturer', 'Serial Number': 'sn', } devices = content.split('Memory Device') for item in devices: item = item.strip() if not item: continue if item.startswith('#'): continue segment = {} lines = item.split('\n\t') for line in lines: if len(line.split(':')) > 1: key, value = line.split(':') else: key = line.split(':')[0] value = "" if key in key_map: if key == 'Size': segment[key_map['Size']] = convert.convert_mb_to_gb(value, 0) else: segment[key_map[key.strip()]] = value.strip() ram_dict[segment['slot']] = segment return ram_dict
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#关于网卡信息 import re import os import traceback from .base import BasePlugin from lib.response import BaseResponse class NicPlugin(BasePlugin): def linux(self): response = BaseResponse() try: if self.test_mode: from config.settings import BASEDIR output = open(os.path.join(BASEDIR, 'files/nic.out'), 'r').read() interfaces_info = self._interfaces_ip(output) else: interfaces_info = self.linux_interfaces() self.standard(interfaces_info) response.data = interfaces_info except Exception as e: msg = "%s linux nic plugin error: %s" self.logger.log(msg % (self.hostname, traceback.format_exc()), False) response.status = False response.error = msg % (self.hostname, traceback.format_exc()) return response def linux_interfaces(self): ''' Obtain interface information for *NIX/BSD variants ''' ifaces = dict() ip_path = 'ip' if ip_path: cmd1 = self.exec_shell_cmd('sudo {0} link show'.format(ip_path)) cmd2 = self.exec_shell_cmd('sudo {0} addr show'.format(ip_path)) ifaces = self._interfaces_ip(cmd1 + '\n' + cmd2) return ifaces def which(self, exe): def _is_executable_file_or_link(exe): # check for os.X_OK doesn't suffice because directory may executable return (os.access(exe, os.X_OK) and (os.path.isfile(exe) or os.path.islink(exe))) if exe: if _is_executable_file_or_link(exe): # executable in cwd or fullpath return exe # default path based on busybox's default default_path = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin' search_path = os.environ.get('PATH', default_path) path_ext = os.environ.get('PATHEXT', '.EXE') ext_list = path_ext.split(';') search_path = search_path.split(os.pathsep) if True: # Add any dirs in the default_path which are not in search_path. If # there was no PATH variable found in os.environ, then this will be # a no-op. This ensures that all dirs in the default_path are # searched, which lets salt.utils.which() work well when invoked by # salt-call running from cron (which, depending on platform, may # have a severely limited PATH). search_path.extend( [ x for x in default_path.split(os.pathsep) if x not in search_path ] ) for path in search_path: full_path = os.path.join(path, exe) if _is_executable_file_or_link(full_path): return full_path return None def _number_of_set_bits_to_ipv4_netmask(self, set_bits): # pylint: disable=C0103 ''' Returns an IPv4 netmask from the integer representation of that mask. Ex. 0xffffff00 -> '255.255.255.0' ''' return self.cidr_to_ipv4_netmask(self._number_of_set_bits(set_bits)) def cidr_to_ipv4_netmask(self, cidr_bits): ''' Returns an IPv4 netmask ''' try: cidr_bits = int(cidr_bits) if not 1 <= cidr_bits <= 32: return '' except ValueError: return '' netmask = '' for idx in range(4): if idx: netmask += '.' if cidr_bits >= 8: netmask += '255' cidr_bits -= 8 else: netmask += '{0:d}'.format(256 - (2 ** (8 - cidr_bits))) cidr_bits = 0 return netmask def _number_of_set_bits(self, x): ''' Returns the number of bits that are set in a 32bit int ''' # Taken from http://stackoverflow.com/a/4912729. Many thanks! x -= (x >> 1) & 0x55555555 x = ((x >> 2) & 0x33333333) + (x & 0x33333333) x = ((x >> 4) + x) & 0x0f0f0f0f x += x >> 8 x += x >> 16 return x & 0x0000003f def _interfaces_ip(self, out): ''' Uses ip to return a dictionary of interfaces with various information about each (up/down state, ip address, netmask, and hwaddr) ''' ret = dict() right_keys = ['name', 'hwaddr', 'up', 'netmask', 'ipaddrs'] def parse_network(value, cols): ''' Return a tuple of ip, netmask, broadcast based on the current set of cols ''' brd = None if '/' in value: # we have a CIDR in this address ip, cidr = value.split('/') # pylint: disable=C0103 else: ip = value # pylint: disable=C0103 cidr = 32 if type_ == 'inet': mask = self.cidr_to_ipv4_netmask(int(cidr)) if 'brd' in cols: brd = cols[cols.index('brd') + 1] return (ip, mask, brd) groups = re.compile('\r?\n\\d').split(out) for group in groups: iface = None data = dict() for line in group.splitlines(): if ' ' not in line: continue match = re.match(r'^\d*:\s+([\w.\-]+)(?:@)?([\w.\-]+)?:\s+<(.+)>', line) if match: iface, parent, attrs = match.groups() if 'UP' in attrs.split(','): data['up'] = True else: data['up'] = False if parent and parent in right_keys: data[parent] = parent continue cols = line.split() if len(cols) >= 2: type_, value = tuple(cols[0:2]) iflabel = cols[-1:][0] if type_ in ('inet',): if 'secondary' not in cols: ipaddr, netmask, broadcast = parse_network(value, cols) if type_ == 'inet': if 'inet' not in data: data['inet'] = list() addr_obj = dict() addr_obj['address'] = ipaddr addr_obj['netmask'] = netmask addr_obj['broadcast'] = broadcast data['inet'].append(addr_obj) else: if 'secondary' not in data: data['secondary'] = list() ip_, mask, brd = parse_network(value, cols) data['secondary'].append({ 'type': type_, 'address': ip_, 'netmask': mask, 'broadcast': brd, }) del ip_, mask, brd elif type_.startswith('link'): data['hwaddr'] = value if iface: if iface.startswith('pan') or iface.startswith('lo') or iface.startswith('v'): del iface, data else: ret[iface] = data del iface, data return ret def standard(self, interfaces_info): for key, value in interfaces_info.items(): ipaddrs = set() netmask = set() if not 'inet' in value: value['ipaddrs'] = '' value['netmask'] = '' else: for item in value['inet']: ipaddrs.add(item['address']) netmask.add(item['netmask']) value['ipaddrs'] = '/'.join(ipaddrs) value['netmask'] = '/'.join(netmask) del value['inet']
(2)client.py文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os import json import time import hashlib import requests from src import plugins from lib.serialize import Json from lib.log import Logger from config import settings from concurrent.futures import ThreadPoolExecutor class AutoBase(object): def __init__(self): self.asset_api = settings.ASSET_API self.key = settings.KEY self.key_name = settings.AUTH_KEY_NAME def auth_key(self): """ 接口认证 :return: """ ha = hashlib.md5(self.key.encode('utf-8')) time_span = time.time() ha.update(bytes("%s|%f" % (self.key, time_span), encoding='utf-8')) encryption = ha.hexdigest() result = "%s|%f" % (encryption, time_span) return {self.key_name: result} def get_asset(self): """ get方式向获取未采集的资产 :return: {"data": [{"hostname": "c1.com"}, {"hostname": "c2.com"}], "error": null, "message": null, "status": true} """ try: headers = {} headers.update(self.auth_key()) response = requests.get( url=self.asset_api, headers=headers ) except Exception as e: response = e return response.json() def post_asset(self, msg, callback=None): """ post方式向接口提交资产信息 :param msg: :param callback: :return: """ status = True try: headers = {} headers.update(self.auth_key()) response = requests.post( url=self.asset_api, headers=headers, json=msg ) except Exception as e: response = e status = False if callback: callback(status, response) def process(self): """ 派生类需要继承此方法,用于处理请求的入口 :return: """ raise NotImplementedError('you must implement process method') def callback(self, status, response): """ 提交资产后的回调函数 :param status: 是否请求成功 :param response: 请求成功,则是响应内容对象;请求错误,则是异常对象 :return: """ if not status: Logger().log(str(response), False) return ret = json.loads(response.text) if ret['code'] == 1000: Logger().log(ret['message'], True) else: Logger().log(ret['message'], False) class AutoAgent(AutoBase): def __init__(self): self.cert_file_path = settings.CERT_FILE_PATH super(AutoAgent, self).__init__() def load_local_cert(self): """ 获取本地以为标识 :return: """ if not os.path.exists(self.cert_file_path): return None with open(self.cert_file_path, mode='r') as f: data = f.read() if not data: return None cert = data.strip() return cert def write_local_cert(self, cert): """ 写入本地以为标识 :param cert: :return: """ if not os.path.exists(self.cert_file_path): os.makedirs(os.path.basename(self.cert_file_path)) with open(settings.CERT_FILE_PATH, mode='w') as f: f.write(cert) def process(self): """ 获取当前资产信息 1. 在资产中获取主机名 cert_new 2. 在本地cert文件中获取主机名 cert_old 如果cert文件中为空,表示是新资产 - 则将 cert_new 写入该文件中,发送数据到服务器(新资产) 如果两个名称不相等 - 如果 db=new 则,表示应该主动修改,new为唯一ID - 如果 db=old 则,表示 :return: """ server_info = plugins.get_server_info() if not server_info.status: return local_cert = self.load_local_cert() if local_cert: if local_cert == server_info.data['hostname']: pass else: server_info.data['hostname'] = local_cert else: self.write_local_cert(server_info.data['hostname']) server_json = Json.dumps(server_info.data) self.post_asset(server_json, self.callback) class AutoSSH(AutoBase): def process(self): """ 根据主机名获取资产信息,将其发送到API :return: """ task = self.get_asset() if not task['status']: Logger().log(task['message'], False) pool = ThreadPoolExecutor(10) for item in task['data']: hostname = item['hostname'] pool.submit(self.run, hostname) pool.shutdown(wait=True) def run(self, hostname): server_info = plugins.get_server_info(hostname) server_json = Json.dumps(server_info.data) self.post_asset(server_json, self.callback) class AutoSalt(AutoBase): def process(self): """ 根据主机名获取资产信息,将其发送到API :return: { "data": [ {"hostname": "c1.com"}, {"hostname": "c2.com"}], "error": null, "message": null, "status": true } """ task = self.get_asset() if not task['status']: Logger().log(task['message'], False) # 创建线程池:最大可用线程10 pool = ThreadPoolExecutor(10) # "data": [ {"hostname": "c1.com"}, {"hostname": "c2.com"}], for item in task['data']: # c1.com c2.com hostname = item['hostname'] pool.submit(self.run, hostname) # run(c1.com) 1 # run(c2.com) 2 pool.shutdown(wait=True) def run(self, hostname): # 获取指定主机名的资产信息 # {'status': True, 'message': None, 'error': None, 'data': {'disk': <lib.response.BaseResponse object at 0x00000000014686A0>, 'main_board': <lib.response.BaseResponse object at 0x00000000014689B0>, 'nic': <lib.response.BaseResponse object at 0x0000000001478278>, 'memory': <lib.response.BaseResponse object at 0x0000000001468F98>, 'os_platform': 'linux', 'os_version': 'CentOS release 6.6 (Final)', 'hostname': 'c1.com', 'cpu': <lib.response.BaseResponse object at 0x0000000001468E10>}} server_info = plugins.get_server_info(hostname) # 序列化成字符串 server_json = Json.dumps(server_info.data) # 发送到API self.post_asset(server_json, self.callback)
(3)scripts.py文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from src.client import AutoAgent from src.client import AutoSSH from src.client import AutoSalt from config import settings def client(): if settings.MODE == 'agent': cli = AutoAgent() elif settings.MODE == 'ssh': cli = AutoSSH() elif settings.MODE == 'salt': cli = AutoSalt() else: raise Exception('请配置资产采集模式,如:ssh、agent、salt') cli.process()
5.log目录(存放日志文件)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#错误日志
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#运行日志