CMDB数据收集客户端(三)

CMDB最主要的管理对象:服务器,其数据信息自然不可能通过手工收集,必须以客户端的方式,定时自动收集并报告给远程的服务器。

下面,让我们暂时忘掉Django,进入Python运维的世界......

一、客户端程序组织

编写客户端,不能一个py脚本包打天下,要有组织有目的,通常我们会采取下面的结构:

mark

在Pycharm下,创建一个Client文件夹,作为客户端的根目录。

在Client下,创建上面的包。注意是包,不是文件夹:

  • bin是客户端启动脚本的所在目录
  • conf是配置文件目录
  • core是核心代码目录
  • log是日志文件保存目录
  • plugins是插件或工具目录

二、开发数据收集客户端

1.程序入口脚本

++在bin目录中新建main.py文件++,写入下面的代码:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import os
import sys

BASE_DIR = os.path.dirname(os.getcwd())
# 设置工作目录,使得包和模块能够正常导入
sys.path.append(BASE_DIR)

from core import handler

if __name__ == '__main__':

    handler.ArgvHandler(sys.argv)   ##获取参数,传入到ArgvHandler()
  • 通过os和sys模块的配合,将当前客户端所在目录设置为工作目录,如果不这么做,会无法导入其它模块;
  • handler模块是核心代码模块,在core目录中,我们一会来实现它。
  • 以后调用客户端就只需要执行python main.py 参数就可以了

++这里有个问题一定要强调一下,那就是Python解释器的调用,执行命令的方式和代码第一行#!/usr/bin/env python的指定方式一定不能冲突,要根据你的实际情况实际操作和修改代码,很多新手连Python本身都没搞明白就上来执行脚本,碰到各种解释器不合法的错误,请回去补足基础!++

2.主功能模块

++在core下,创建handler.py文件++,写入下面的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2019-3-2 13:51
# @Author  : zhdya@zhdya.cn
# @File    : handler.py


import json
import time
import urllib.parse
import urllib.request
from core import info_collection
from conf import settings

class ArgvHandler(object):

    def __init__(self, args):
        self.args = args
        self.parse_args()

    def parse_args(self):
        """
        分析参数,如果有参数指定的功能,则执行该功能,如果没有,打印帮助说明。
        :return:
        """
        if len(self.args) > 1 and hasattr(self, self.args[1]):
            func = getattr(self, self.args[1])
            func()
        else:
            self.help_msg()     ##如果执行程序没有带参数就会提示如下信息:help_msg()

    @staticmethod       #静态方法 类或实例均可调用,静态方法函数里不传入self,这样如上self.help_msg()就可以调用了
    def help_msg():
        """
        帮助说明
        :return:
        """
        msg = '''
        collect_data        收集硬件信息
        report_data         收集硬件信息并汇报
        '''
        print(msg)

    @staticmethod
    def collect_data():
        """收集硬件信息,用于测试!"""
        info = info_collection.InfoCollection()
        asset_data = info.collect()
        print(asset_data)

    @staticmethod
    def report_data():
        """
        收集硬件信息,然后发送到服务器。
        :return:
        """
        # 收集信息
        info = info_collection.InfoCollection()
        asset_data = info.collect()
        # 将数据打包到一个字典内,并转换为json格式
        data = {"asset_data": json.dumps(asset_data)}
        print("handler_data-->>", data)
        # 根据settings中的配置,构造url
        url = "http://%s:%s%s" % (settings.Params['server'], settings.Params['port'], settings.Params['url'])
        print("handler_url-->>", url)
        print('正在将数据发送至: [%s]  ......' % url)
        try:
            # 使用Python内置的urllib.request库,发送post请求。
            # 需要先将数据进行封装,并转换成bytes类型
            data_encode = urllib.parse.urlencode(data).encode()
            print("handler_data_encode-->>", data_encode)
            response = urllib.request.urlopen(url=url, data=data_encode, timeout=settings.Params['request_timeout'])
            print("\033[31;1m发送完毕!\033[0m ")
            message = response.read().decode()
            print("返回结果:%s" % message)
        except Exception as e:
            message = "发送失败"
            print("\033[31;1m发送失败,%s\033[0m" % e)
        # 记录发送日志
        with open(settings.PATH, 'ab') as f:        ##a追加,b二进制文件
            string = '发送时间:%s \t 服务器地址:%s \t 返回结果:%s \n' % (time.strftime('%Y-%m-%d %H:%M:%S'), url, message)
            f.write(string.encode())
            print("日志记录成功!")

说明:

  • handler模块中只有一个ArgvHandler类;
  • 在main模块中也是实例化了一个ArgvHandler类的对象,并将调用参数传递进去;
  • 首先,初始化方法会保存调用参数,然后执行parse_args()方法分析参数;
  • 如果ArgvHandler类有参数指定的功能,则执行该功能,如果没有,打印帮助说明。
  • 目前ArgvHandler类只有两个核心方法:collect_data和report_dataa;
  • 这两个方法一个是收集数据并打印到屏幕,用于测试;report_data方法才会将实际的数据发往服务器。
  • 数据的收集由info_collection.InfoCollection类负责,一会再看;
  • report_data方法会将收集到的数据打包到一个字典内,并转换为json格式;
  • 然后通过settings中的配置,构造发送目的地url;
  • 通过Python内置的urllib.parse对数据进行封装;
  • 通过urllib.request将数据发送到目的url;
  • 接收服务器返回的信息;
  • 将成功或者失败的信息写入日志文件中。

以后,我们要测试数据收集,执行:

python main.py collect_data
要实际往服务器发送收集到的数据,则执行:
python main.py report_data

3.配置文件

要将所有可能修改的数据、常量、配置等都尽量以配置文件的形式组织起来,尽量不要在代码中写死任何数据。

++在conf中,新建settings.py文件++,写入下面的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2019-3-2 14:21
# @Author  : zhdya@zhdya.cn
# @File    : settings.py

import os

# 远端服务器配置
Params = {
    "server": "10.10.7.26",
    "port": 8000,
    'url': '/assets/report/',
    'request_timeout': 30,
}

# 日志文件配置

PATH = os.path.join(os.path.dirname(os.getcwd()), 'log', 'cmdb.log')
print("conf_settings-->>", PATH)

# 更多配置,请都集中在此文件中
这里,配置了服务器地址、端口、发送的url、请求的超时时间,以及日志文件路径。请根据你的实际情况进行修改。

==如上server端我是直接启动的django服务 也就是如上10.10.7.26是我笔记本的IP地址,这样默认我笔记本的10.10.7.26:8000就对外开放了!==

4.信息收集模块

++在core中新建info_collection.py文件++,写入下面的代码:

==<关于如下:from plugins.linux import sys_info 以及 from plugins.windows import sys_info as win_sys_info 稍后章节我们会建立一系列的目录!!>==

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2019-3-2 14:48
# @Author  : zhdya@zhdya.cn
# @File    : info_collection.py


import sys
import platform


def linux_sys_info():
    from plugins.linux import sys_info
    return sys_info.collect()


def windows_sys_info():
    from plugins.windows import sys_info as win_sys_info
    return win_sys_info.collect()


class InfoCollection(object):

    def collect(self):
        # 收集平台信息
        # 首先判断当前平台,根据平台的不同,执行不同的方法
        try:
            func = getattr(self, platform.system())
            info_data = func()
            formatted_data = self.build_report_data(info_data)
            return formatted_data
        except AttributeError:
            sys.exit("不支持当前操作系统: [%s]! " % platform.system())

    def Linux(self):

        return linux_sys_info()

    def Windows(self):
        return windows_sys_info()

    def build_report_data(self, data):
        # 留下一个接口,方便以后增加功能或者过滤数据
        return data
该模块的作用很简单:

  • 首先通过Python内置的platform模块获取执行main脚本的操作系统类别,通常是windows和Linux;
  • 根据操作系统的不同,反射获取相应的信息收集方法,并执行
  • 如果是客户端不支持的操作系统,比如苹果系统,则提示并退出客户端。

因为windows和Linux两大操作系统的巨大平台差异,我们必须写两个收集信息的脚本。

到目前未知,我们的客户端结构如下图所示:

mark