Patroler

Patroler

V0.0.2更新

1 功能

  1. 支持SSH、Telnet方式收集设备命令。
  2. 支持自定义端口
  3. 支持多线程
  4. excel表中,端口列可指定或不填写保持默认;项目必填
  5. 生成访问出错日志:patroler.log
  6. 收集日志:存放在patrol_data文件夹中。

食用方式

    Patroler v0.0.2 设备巡检信息收集工具

      ____      _      _____   ____   U  ___ u  _    U _____ u  ____
    U|  _"\ U  /"\  u |_ " _U |  _"\ u \/"_ \/ |"|   \| ___"|U |  _"\ u
    \| |_) |/\/ _ \/    | |  \| |_) |/ | | | U | | u  |  _|"  \| |_) |/
     |  __/  / ___ \   /| |\  |  _ .-,_| |_| |\| |/__ | |___   |  _ <
     |_|    /_/   \_\ u |_|U  |_| \_\_)-\___/  |_____||_____|  |_| \_\
     ||>>_   \\    >> _// \\_ //   \\_   \\    //  \\ <<   >>  //   \\_
    (__)__) (__)  (__(__) (__(__)  (__) (__)  (_")("_(__) (__)(__)  (__)

    Patroler is under development, any issue please contact @fcarey!

options:
  -h, --help            show this help message and exit
  -f FILE, --file FILE  巡检清单,excel格式文件:必填
  -cf CMDFILE, --cmdfile CMDFILE
                        巡检命令:必填
  -t THREAD, --thread THREAD
                        同时巡检设备的数量,默认5
  -e, --getexcel        生成巡检清单模板:patrol_devices_19970101.xlsx
  -c, --getcmd          生成建议巡检命令文件:patrol_cmd_1997010.txt

Example:
        Patroler.exe -f 巡检设备列表.xlsx -cf patroler_cmds.txt -t 5

源码

# !/usr/bin/env Python
# coding=utf-8
import argparse
import sys
import os
import telnetlib
import chardet
import paramiko
import time
import pandas as pd
import threading
import logging
from logging import handlers


def banner():
    yellow = '\033[01;33m'
    white = '\033[01;37m'
    green = '\033[01;32m'
    blue = '\033[01;34m'
    red = '\033[1;31m'
    end = '\033[0m'

    version = 'v0.0.2'
    message = white + red + version + white

    patrol_banner = f"""
    Patroler {message} 设备巡检信息收集工具{yellow}

      ____      _      _____   ____   U  ___ u  _    U _____ u  ____     
    U|  _"\\ U  /"\\  u |_ " _U |  _"\\ u \/"_ \/ |"|   \| ___"|U |  _"\\ u   {green}
    \| |_) |/\/ _ \/    | |  \| |_) |/ | | | U | | u  |  _|"  \| |_) |/  
     |  __/  / ___ \   /| |\  |  _ .-,_| |_| |\| |/__ | |___   |  _ <     {blue}
     |_|    /_/   \_\ u |_|U  |_| \_\_)-\___/  |_____||_____|  |_| \_\   
     ||>>_   \\\\    >> _// \\\\_ //   \\\\_   \\\\    //  \\\\ <<   >>  //   \\\\_   {red}
    (__)__) (__)  (__(__) (__(__)  (__) (__)  (_")("_(__) (__)(__)  (__) 

    {white}Patroler is under development, any issue please contact {blue}@fcarey{red}!{end}
    """
    print(patrol_banner)


class Logger(object):
    level_relations = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
        'crit': logging.CRITICAL
    }  # 日志级别关系映射

    def __init__(self, filename, level='info', when='D', backCount=3,
                 fmt='%(asctime)s - %(levelname)s: %(message)s'):
        self.logger = logging.getLogger(filename)
        format_str = logging.Formatter(fmt)  # 设置日志格式
        self.logger.setLevel(self.level_relations.get(level))  # 设置日志级别
        sh = logging.StreamHandler()  # 往屏幕上输出
        sh.setFormatter(format_str)  # 设置屏幕上显示的格式
        th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount,
                                               encoding='utf-8')  # 往文件里写入#指定间隔时间自动生成文件的处理器
        # 实例化TimedRotatingFileHandler
        # interval是时间间隔,backupCount是备份文件的个数,如果超过这个个数,就会自动删除,when是间隔的时间单位,单位有以下几种:
        # S 秒
        # M 分
        # H 小时、
        # D 天、
        # W 每星期(interval==0时代表星期一)
        # midnight 每天凌晨
        th.setFormatter(format_str)  # 设置文件里写入的格式
        self.logger.addHandler(sh)  # 把对象加到logger里
        self.logger.addHandler(th)


class PatrolerParser(argparse.ArgumentParser):
    """
    重写argparse,实现捕获ArgumentParser在出错时引发的SystemExit异常,打印帮助,然后再次引发异常退出。
    """

    def error(self, message=''):
        # sys.stderr.write('error: %s\n' % message)
        self.print_help()
        print("\nExample: \n\tPatroler.exe -f 巡检设备列表.xlsx -cf patroler_cmds.txt -t 5")
        sys.exit(2)


def getOptions(args):
    """
    打印帮助信息。
    :param args:
    :return:
    """
    getparser = PatrolerParser(usage=argparse.SUPPRESS)
    getparser.add_argument("-f", "--file", type=str, help="巡检清单,excel格式文件:必填")
    getparser.add_argument("-cf", "--cmdfile", type=str, help="巡检命令:必填")
    getparser.add_argument("-t", "--thread", type=int, default=5, help="同时巡检设备的数量,默认5")
    getparser.add_argument("-e", "--getexcel", action='store_true', help="生成巡检清单模板:patrol_devices_19970101.xlsx")
    getparser.add_argument("-c", "--getcmd", action='store_true', help="生成建议巡检命令文件:patrol_cmd_1997010.txt")
    # getparser.add_argument("-o", "--output", help="Your destination output file.")
    if len(args) > 0:
        patroler_options = getparser.parse_args(args)
        return patroler_options
    else:
        getparser.error()


def get_excel_temp():
    """
    生成设备列表Excel模板文件
    :return:
    """
    tempData = pd.DataFrame({
        '序号': [1, 2, 3],
        '管理IP': ['172.16.1.1', '172.16.1.2', '172.16.1.3'],
        '设备名称': ['device01', 'device02', 'device03'],
        '用户名': ['admin', 'admin', 'admin'],
        '密码': ['123456', '3456789', '4567890'],
        '登陆方式': ['telnet', 'ssh', 'ssh'],
        '端口': [23, 2222, 22]
    })
    cur_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
    filename = 'patrol_devices_' + cur_time + '.xlsx'
    tempData.to_excel(filename, sheet_name="sheet1", index=False)
    print('生成巡检清单"%s"成功.' % filename)
    sys.exit(0)


def get_advice_cmd():
    """
    生成推荐巡检清单。
    :return:
    """
    patrol_cmd = ['show version detail\n', 'show clock\n', 'show session generic\n', 'show session generic detail\n',
                  'show memory\n', 'show memory detail\n', 'show cpu \n', 'show cpu detail\n', 'show interface\n',
                  'show alg\n', 'show ip route\n', 'show module\n', 'show configuration | include hostname\n',
                  'show environment\n', 'show app info\n', 'show app update\n', 'show ips status\n',
                  'show ips signature info\n', 'show capacity all\n', 'show dp-filter\n', 'show dp filter\n',
                  'show snat resource\n', 'show dnat rule\n', 'show snat rule\n', 'show snat\n', 'show dnat\n',
                  'show snat | include snat rules total number\n', 'show dnat | include dnat rules total number\n',
                  'show policy | include Total rules count\n', 'show address | include Total rules count\n',
                  'show service userdefine | include Total configured\n', 'show license\n', 'show arp\n',
                  'show inventory\n', 'show ha group config\n', 'show ha group 0\n', 'show ha link status\n',
                  'show ha sync statistic ntp\n', 'show ha sync statistic config\n',
                  'show logging event | include 2022\n',
                  'show logg alarm | include 2022\n', 'show logg security | include 2022']

    cur_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
    filename = 'patrol_cmds_' + cur_time + '.txt'
    with open(filename, 'w', encoding='utf-8') as f:
        for cmd in patrol_cmd:
            f.write(cmd)
    print('生成建议巡检命令文件"%s"成功.' % filename)
    sys.exit(0)


def mklogdir():
    """
    生成日志文件夹
    :return:
    """
    cur_time = time.strftime('%Y%m%d', time.localtime())
    dirname = 'patrol_data_' + cur_time

    if not os.path.exists(dirname):  # 判断目标目录是否存在
        os.mkdir(dirname)  # 如果不存在则创建目标目录
        print("已创建数据存放目录:%s" % dirname)
    return dirname


def existsfile(filename):
    if not os.path.exists(filename):  # 判断目标文件是否存在
        log.logger.error('%s 不存在,请重新输入!' % filename)
        getOptions(args='')
        sys.exit(2)
    else:
        return filename


def check_code(file):
    """
    检查文件所属编码
    :param file:文件名称
    :return:文件所属编码
    """
    file_text = open(file, 'rb').readline()
    file_code = chardet.detect(file_text)['encoding']
    # 由于windows系统的编码有可能是Windows-1254,打印出来后还是乱码,所以不直接用adchar['encoding']编码
    if file_code == 'gbk' or file_code == 'GBK' or file_code == 'GB2312':
        file_code = 'GB2312'
    else:
        file_code = 'utf-8'
    return file_code


class PatrolSSH(object):
    def __init__(self, dirname, cmdfile, cmdfile_code, host, dev_name, user, passwd, port=22):
        self.cmdfile_code = cmdfile_code
        self.host = host
        self.dev_name = dev_name
        self.user = user
        self.passwd = passwd
        self.port = port
        self.cmdfile = cmdfile
        self.dirname = dirname

    def ssh_login(self):
        """
        实例化ssh登陆
        :return:
        """
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
        try:
            ssh_client.connect(hostname=self.host, username=self.user, password=self.passwd, port=self.port, timeout=10,
                               look_for_keys=False, allow_agent=False)
        except (TimeoutError, Exception) as e:
            message = '{}:{} \t无法访问,'.format(self.host, self.port) + str(e)
            log.logger.error(message)
            pool_sema.release()  # 解锁
            sys.exit(1)
        else:
            return ssh_client

    def process_ssh_patrol_data(self, session, cmds, savefile):
        """
        :param session: 实例化的ssh
        :param cmds: 巡检命令列表
        :param savefile: 需要保存的文件名称
        :return:
        """
        # invoke_shell()函数类似shell终端,可以将执行结果分批次返回,看到任务的执行情况,不会因为执行一个很长的脚本而不知道是否执行成功
        shell = session.invoke_shell()
        pre_commands = ['ter len 0\n', 'ter width 512\n']
        for pre_cmd in pre_commands:
            time.sleep(0.5)
            # 发送命令
            shell.send(pre_cmd.encode('utf-8'))
        cmds[-1] = cmds[-1] + '\n'
        for patrol_cmd in cmds:
            print('正在处理{0}: {1}'.format(self.host, patrol_cmd.replace('\n', '')))
            time.sleep(0.5)
            shell.send(patrol_cmd.encode('utf-8'))
            time.sleep(0.5)
            # 确认是否还有数据需要返回
            recv_flag = shell.recv_ready()
            while recv_flag:
                result = shell.recv(65535).decode(encoding='utf-8', errors='ignore')
                savefile.write(result)
                savefile.flush()
                time.sleep(0.5)
                recv_flag = shell.recv_ready()
        shell.close()

    def get_ssh_patrol_data(self):
        """
        获取返回数据
        :return:
        """
        session = self.ssh_login()
        # 单独采集命令
        one_cmds = ['show config']
        for one_cmd in one_cmds:
            with open(self.dirname + '\\' + self.host + '_' + self.dev_name + '_' + one_cmd + '.txt', 'w',
                      encoding='utf-8',
                      newline='') as one_cmd_config:
                self.process_ssh_patrol_data(session=session, cmds=[one_cmd], savefile=one_cmd_config)
                log.logger.info('%s 命令 %s 收集完成' % (self.host, one_cmd))

        # 巡检命令信息采集
        patrol_cmd = open(self.cmdfile, 'r', encoding=self.cmdfile_code).readlines()
        with open(self.dirname + '\\' + self.host + '_' + self.dev_name + '.txt', 'w', encoding='utf-8',
                  newline='') as patrol_data:
            self.process_ssh_patrol_data(session=session, cmds=patrol_cmd, savefile=patrol_data)
            log.logger.info('%s 巡检命令收集完成' % self.host)
        session.close()
        pool_sema.release()  # 解锁


class PatrolTelnet(object):
    def __init__(self, dirname, cmdfile, cmdfile_code, host, dev_name, user, passwd, port=23):
        self.cmdfile_code = cmdfile_code
        self.host = host
        self.dev_name = dev_name
        self.user = user
        self.passwd = passwd
        self.port = port
        self.cmdfile = cmdfile
        self.dirname = dirname

    def telnet_login(self):
        telnet_client = telnetlib.Telnet()
        try:
            telnet_client.open(host=self.host, port=self.port)
        except (TimeoutError, Exception) as e:
            message = '{}:{} \t无法访问,'.format(self.host, self.port) + str(e)
            log.logger.error(message)
            pool_sema.release()  # 解锁
            sys.exit(2)
        else:
            # 等待login出现后输入用户名,最多等待10秒
            telnet_client.read_until(b'login: ', timeout=10)
            telnet_client.write(self.user.encode('ascii') + b'\n')
            # 等待Password出现后输入用户名,最多等待10秒
            telnet_client.read_until(b'assword: ', timeout=10)
            telnet_client.write(self.passwd.encode('ascii') + b'\n')
            # 延时两秒再收取返回结果,给服务端足够响应时间
            time.sleep(1)
            # 获取登录结果
            # read_very_eager()获取到的是上次获取之后,本次获取之前的所有输出
            command_result = telnet_client.read_very_eager().decode()
            # 获取当前telnet窗口中设备名标识
            flag = command_result.split('\n')[-1]
            if 'Login incorrect' not in command_result:
                log.logger.info('%s 登录成功' % self.host)
            else:
                log.logger.warning('%s \t登录失败,用户名或密码错误。' % self.host)
                telnet_client.close()
                pool_sema.release()  # 解锁
                sys.exit(1)
            return telnet_client, flag

    def process_telnet_patrol_data(self, flag, session, cmds, savefile):
        pre_commands = ['ter len 0\n', 'ter width 512\n']
        for pre_cmd in pre_commands:
            session.write(pre_cmd.encode('utf-8'))
            session.read_until(flag.encode('utf-8'))
        cmds[-1] = cmds[-1] + '\n'
        for patrol_cmd in cmds:
            print('正在处理{0}: {1}'.format(self.host, patrol_cmd.replace('\n', '')))
            session.write(patrol_cmd.encode())
            result = session.read_until(flag.encode('utf-8'))
            savefile.write(result.decode('utf-8'))
            savefile.flush()

    def get_telnet_patrol_data(self):
        session, flag = self.telnet_login()
        # 单独采集命令
        one_cmds = ['show config']
        for one_cmd in one_cmds:
            with open(self.dirname + '\\' + self.host + '_' + self.dev_name + '_' + one_cmd + '.txt', 'w',
                      encoding='utf-8',
                      newline='') as one_cmd_config:
                self.process_telnet_patrol_data(flag, session=session, cmds=[one_cmd], savefile=one_cmd_config)
                log.logger.info('%s 命令 %s 收集完成' % (self.host, one_cmd))

        # 巡检命令信息采集
        patrol_cmd = open(self.cmdfile, 'r', encoding=self.cmdfile_code).readlines()
        with open(self.dirname + '\\' + self.host + '_' + self.dev_name + '.txt', 'w', encoding='utf-8',
                  newline='') as patrol_data:
            self.process_telnet_patrol_data(flag, session=session, cmds=patrol_cmd, savefile=patrol_data)
            log.logger.info('%s 巡检命令收集完成' % self.host)
        session.close()
        pool_sema.release()  # 解锁


def run(dirname, cmdfile, cmdfile_code, host, dev_name, user, passwd, method, port):
    if method == 'ssh':
        PatrolSSH(dirname, cmdfile, cmdfile_code=cmdfile_code, host=host, dev_name=dev_name, user=user,
                  passwd=passwd, port=port).get_ssh_patrol_data()
    elif method == 'telnet':
        PatrolTelnet(dirname, cmdfile, cmdfile_code=cmdfile_code, host=host, dev_name=dev_name, user=user,
                     passwd=passwd, port=port).get_telnet_patrol_data()


if __name__ == '__main__':
    banner()
    log = Logger('patroler.log', level='debug')
    get_options = getOptions(sys.argv[1:])
    if get_options.getcmd:
        get_advice_cmd()
    elif get_options.getexcel:
        get_excel_temp()
    device_file = existsfile(get_options.file)
    cmdfile = existsfile(get_options.cmdfile)
    cmdfile_code = check_code(cmdfile)
    thread_num = get_options.thread
    dirname = mklogdir()

    pool_sema = threading.BoundedSemaphore(thread_num)
    df = pd.read_excel(io=device_file)
    threads_list = []
    # 迭代数据,以键值对的形式 获取每行的数据,注意处理空值。
    for idx, row in df.iterrows():
        if pd.isna(row[6]):
            if row[5] == 'ssh':
                row[6] = 22
            elif row[5] == 'telnet':
                row[6] = 23
        else:
            row[6] = int(row[6])
        pool_sema.acquire()
        mythread = threading.Thread(
            target=run, args=(dirname, cmdfile, cmdfile_code, row[1], row[2], row[3],
                              row[4], row[5], row[6]))
        mythread.start()
        threads_list.append(mythread)
    for t in threads_list:
        t.join()
posted @ 2023-02-13 16:00  f_carey  阅读(2)  评论(0编辑  收藏  举报