CMDB资产采集

CMDB资产采集

一、概述

CMDB --Configuration Management Database 配置管理数据库, CMDB存储与管理企业IT架构中设备的各种配置信息,它与所有服务支持和服务交付流程都紧密相联,支持这些流程的运转、发挥配置信息的价值,同时依赖于相关流程保证数据的准确性。

二、资产采集方式

1.Agent方式

需要在每台服务器上安装Agent,然后每台服务器定时自动去获取信息,发送带数据库,然后后台获取数据进行处理,不过一般我们不会直接将数据直接传递到数据库,会将数据传递到API接口先进行处理,过滤,然后才会发送到数据库。具体流程如下图所示:

采集方法:

#放置在Agent机器上
import subprocess
import requests


######################采集数据################
result = subprocess.getoutput('ipconfig')
#result正则表达式处理获取想要的数据

#整理资产信息
data_dict = {

}

#####################发送数据#################
requests.post('',data=data_dict)    #第一参数为API的url,第二需要发送的数据
python实现agent

2.SSH方式

SSH方式需要使用paramiko模块,通过中控机服务器统一去获取指定服务器的信息。具体流程如下图所示:

采集方法:

#放置在资产采集机器上
#基于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,第二需要发送的数据
python实现ssh

3.saltstack方式

使用master对slave进行操作,类似于列队实现。具体流程如下图所示:

采集方法:

#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)
python实现saltstack

4.puppet方式

puppet方式使用ruby写的,了解即可。具体流程如下图所示:

三、资产采集开发

1.bin目录(存放启动文件)

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()
start.py启动文件

2.config目录(存放配置文件)

#!/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 数据库中资产不存在
"""
setting.py文件
#含有主机名信息,作为CMDB项目采集资产数据唯一标识规定
cert文件

3.lib目录(存放自定义文件)

#定义一种数据格式用于在客户端传递
class BaseResponse(object):
    def __init__(self):
        self.status = True
        self.message = None
        self.data = None
        self.error = None
response.py文件
#自定义序列化
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)
serialize.py文件

4.src目录(存放业务逻辑文件)

(1)plugins目录

#关于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
cpu.py
#关于基本信息
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
basic.py
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__)
__init__.py
#定义基类,实现部分方法
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.')
base.py
#关于硬盘信息
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
disk.py
#关于主板信息
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
main_board.py
#关于内存信息
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
memory.py
#关于网卡信息
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']
nic.py

(2)client.py文件

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)
client.py

(3)scripts.py文件

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()
scripts.py

5.log目录(存放日志文件)

#错误日志
error.log文件
#运行日志
run.log文件
posted @ 2020-01-02 22:05  流浪代码  阅读(487)  评论(0编辑  收藏  举报