netmiko+textfsm 设备巡检自动化

环境

python 3.7 以上

netmiko 4.1.2

盛科 or 华为交换机

tree

.
├── conf.json
├── health.py
├── __init__.py
├── log
└── textfsm
  ├── centec_os_show_environment.textfsm
  ├── centec_os_show_interface_eth.textfsm
  ├── centec_os_show_interface_status.textfsm
  ├── centec_os_show_ip_ospf_neighbor_detail.textfsm
  ├── centec_os_show_process_cpu_history.textfsm
  ├── centec_os_show_process_memory_sorted.textfsm
  ├── centec_os_show_version.textfsm
  ├── huawei_display_ospf_peer.textfsm
  ├── huawei_vrp_display_cpu-usage.textfsm
  ├── huawei_vrp_display_device.textfsm
  ├── huawei_vrp_display_interface_brief.textfsm
  ├── huawei_vrp_display_interface_xg.textfsm
  ├── huawei_vrp_display_memory-usage.textfsm
  ├── huawei_vrp_display_version.textfsm
  └── index

conf.json

配置文件,配置需要巡检的设备ip,用户名,key等,配置发送邮件使用的相关参数

log

记录错误日志,netmiko日志等

health.py

运行脚本

textfsm文件夹

index 命令索引

.textfsm解析命令返回结果的textfsm文件,其他型号交换机只需要保持Value字段不变,修改匹配字符,添加index就可以正常使用

 运行效果

conf.json

{
    "host":[
        {
            "device_ip":"10.0.3.105",
            "device_type":"huawei_vrpv8",
            "port":22,
            "username":"admin",
            "key_file":"",
            "use_key":"False"
        },
        {
            "device_ip":"10.0.3.106",
            "device_type":"centec_os",
            "port":22,
            "username":"admin-rsa",
            "key_file":"~/.ssh/id_rsa",
            "use_key":"True"
        },
        {
            "device_ip":"10.0.3.102",
            "device_type":"centec_os",
            "port":22,
            "username":"admin-rsa",
            "key_file":"~/.ssh/id_rsa",
            "use_key":"True"
        }
        ],
    "use_key":"False",
    "mail":{
        "mail_username":"976584601@qq.com",
        "mail_passwd":"xxxxxx",
        "smtp_server":"smtp.qq.com",
        "sender":"xxx",
        "from_addr":"976584601@qq.com",
        "mail_list":["xxxx@xxxx.com","cs11241991@163.com"]
    }
}

health.py

#!/usr/bin/env python
#-*-coding:utf-8-*-
from netmiko import ConnectHandler
import getpass,json,sys,os,time
from concurrent import futures 
import logging
import email
import smtplib
from email.header import Header
from email.utils import formataddr
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def logger():
    log_name = BaseDir+'/log'
    logger = logging.getLogger()
    fh = logging.FileHandler(log_name)
    formater = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
    fh.setFormatter(formater)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(fh)
    return logger

class Exec:
    def __init__(self,host,passwd):
        self.device_ip,self.device_type = host['device_ip'],host['device_type']
        self.username = host['username']
        self.passwd = passwd
        self.key_file = host['key_file']
        self.use_key = host['use_key']
        self.port = host['port']
    def conn(self):
        dev = {
        'device_type': self.device_type,
        'host': self.device_ip,
        'username': self.username,
        'password': self.passwd,
        'use_keys': bool(self.use_key == 'True'),
        'key_file': self.key_file,
        'disabled_algorithms' : {'pubkeys': ['rsa-sha2-256', 'rsa-sha2-512']},
        'port': self.port
        }
        return ConnectHandler(**dev)
    def textfsm(self,commands):
        ret = []
        with self.conn() as conn:
            for command in commands:
                ret.append(conn.send_command(command,use_textfsm=True)) 
        return ret
class value:
    def __init__(self,conf):
        self.BaseDir = BaseDir
        self.conf = conf
        self.hosts = conf['host']
        self.mail_conf = conf['mail']
        self.status={}
        self.passwd = '' if self.conf['use_key'] =='True' else getpass.getpass()
    def tohtml(self,content):
        html = '<table>'
        th = '<tr style="text-align:left;"><th>{}</th><th>{}</th></tr>'
        td = '<tr style="color: {};"><td>{}</td><td>{}</td></tr>'
        for host in content:
            html+=th.format(content[host]['version'][0]['hostname'],host)
            for module in content[host]:
                if  isinstance(content[host][module],list):
                    for tag in content[host][module]:
                        color = 'red' if '异常' in tag else 'green' 
                        html+=td.format(color,module,tag)
                else:
                    color = 'red' if '异常' in content[host][module] else 'green'
                    html+= td.format(color,module,content[host][module])
        html+='</table>'
        return html
    def sendmail(self):
        content = self.tohtml(self.status)
        subject = '%s日常巡检'%(time.strftime('%Y-%m-%d',time.localtime()))
        username,passwd = self.mail_conf["mail_username"],self.mail_conf["mail_passwd"]
        smtp_server,sender = self.mail_conf["smtp_server"],self.mail_conf["sender"]
        from_addr,mail_list = self.mail_conf["from_addr"],self.mail_conf["mail_list"]
        msg = MIMEMultipart('alternative')
        msg.attach(MIMEText(content, 'html'))
        msg['from'] = formataddr([sender,from_addr])
        msg['to'] = ','.join(mail_list)
        msg['subject'] = subject
        try:
            service = smtplib.SMTP(smtp_server,timeout=5)
            service.login(username,passwd)
            service.sendmail(from_addr,mail_list,msg.as_string())
            service.quit()
        except Exception as mail_error:
            log.debug(mail_error)
    def get_value(self,host):
        UseInterface=[]
        device_type = host['device_type']
        if  'centec' in device_type: 
            flag = True  
        elif 'huawei' in device_type: 
            flag= False 
        version_command = 'show version' if flag else 'display version'
        interface_status_command = 'show interface status' if flag else 'display interface brief'
        cpu_command = 'show process cpu h' if flag else 'display cpu-usage'
        memory_command = 'show process memory s' if flag else 'display memory-usage'
        ospf_command = 'show ip ospf nei d'if flag else 'display ospf peer'
        interface_error_command = 'show interface {}'if flag else 'display interface {}'
        obj = Exec(host,self.passwd)
        commands = [version_command,interface_status_command,cpu_command,memory_command,ospf_command]
        version,Interface,CpuUse,MemoryUse,OspfPeers = obj.textfsm(commands)
        for dic in Interface:
            if dic['status'] == 'up' : UseInterface.append(dic['port'])
        error_commands = [interface_error_command.format(port) for port in UseInterface]
        porterror_list = obj.textfsm(error_commands)
        return version,CpuUse,MemoryUse,OspfPeers,porterror_list
    def format_data(self,host):
        try:
            device_ip = host['device_ip']
            version,CpuUse,MemoryUse,OspfPeers,porterror_list = self.get_value(host)
            self.status[device_ip]={
                                    'version':version,
                                    'cpu':self.format_cpu(CpuUse),
                                    'memory':self.format_memory(MemoryUse),
                                    'ospf':self.format_ospf(OspfPeers),
                                    'portstatus':self.format_porterror(porterror_list)
                                    }
            print(version[0]['hostname'],device_ip,'巡检已完成')
        except Exception as format_error:
            log.debug(format_error)
    def format_cpu(self,CpuUse):
        for key,value in CpuUse[0].items():cpustatus = 'CPU使用率异常{} {}%'.format(key,value) if float(value)>50 else 'CPU 正常{} {}%'.format(key,value)
        return cpustatus
    def format_memory(self,MemoryUse):
        percent =  int(int(MemoryUse[0]['used'])/int(MemoryUse[0]['total'])*100)
        memorystatus = '内存状态异常,使用率{}%'.format(percent) if percent > 60 else '内存状态正常,使用率{}%'.format(percent)
        return memorystatus
    def format_porterror(self,porterror_list):
        portstatus=[]
        for porterror in porterror_list:
            porterror = porterror[0]
            outdiscard = 0 if not porterror['outdiscard'] else int(porterror['outdiscard'])
            if int(porterror['inputerror']) or int(porterror['inputcrc']) or outdiscard :
                portstatus.append(porterror['port']+'端口异常存在错误包inputerror{},inputcrc{},outdiscard{}'.format(porterror['inputerror'],porterror['inputcrc'],porterror['outdiscard']))
            else:
                portstatus.append(porterror['port']+'端口状态正常'+' 端口速率{}'.format(porterror['speed']))
        return portstatus
    def format_ospf(self,OspfPeers):
        ospfstatu = ''
        OspfstatuList=[]
        if OspfPeers:
            for peer in OspfPeers:
                *h,m,s = peer['uptime'].split(':')
                h,limit,uptime=(int(m),60,'{}:{}'.format(m,s)) if not h else (int(h[0]),1,'{}:{}:{}'.format(h[0],m,s))
                ospfstatu = 'peer:{}异常 {}:{}前发生中断'.format(peer['peer'],m,s) if h<limit else 'peer:{} 状态正常已运行 {}'.format(peer['peer'],uptime)
                OspfstatuList.append(ospfstatu)
        return OspfstatuList    
    def start(self):
        with futures.ThreadPoolExecutor(100) as executor:
            res = executor.map(self.format_data, [host for host in self.hosts])
        self.sendmail()
        print('ok')
if __name__ == '__main__':
    BaseDir = os.path.dirname(os.path.realpath(sys.argv[0]))
    os.environ['NET_TEXTFSM'] = BaseDir+'/textfsm'
    log = logger()
    with open(BaseDir+"/conf.json","r") as a:
        conf =json.loads(a.read())
    value(conf).start()

textfsm

#centec_os_show_interface_eth.textfsm
Value Port (eth\S+)
Value Speed (\S+)
Value InputError (\d+)
Value InputCrc (\d+)
Value OutDiscard (\d+)




Start
  ^Interface\s+${Port}
  ^\s+Speed\s+\S+\s+${Speed}
  ^.*giants,\s+${InputError}\s+input errors,\s+${InputCrc}\s+CRC
  ^\s+${OutDiscard}\s+output discard -> Record

--------------------------------------------------------------------------------------------------------------------------


#centec_os_show_interface_status.textfsm
Value Port (eth\S+)
Value Status (\S+)

Start
  ^${Port}\s+${Status} -> Record


------------------------------------------------------------------------------------------------------------------------

#centec_os_show_ip_ospf_neighbor_detail.textfsm
Value Peer (\S+)
Value Uptime (\S+)

Start
  ^ Neighbor \S+, interface address \S+ -> Continue.Record
  ^\s+Neighbor\s+${Peer},
  ^\s+Neighbor is up for\s+${Uptime}


------------------------------------------------------------------------------------------------------------------------


#centec_os_show_process_cpu_history.textfsm
Value FiveSeconds (\S+)
Value OneMinute (\S+)
Value FiveMinutes (\S+)

Start
  ^.*seconds:\s+${FiveSeconds}%;.*minute:\s+${OneMinute}%;.*minutes:\s+${FiveMinutes}% -> Record


-------------------------------------------------------------------------------------------------------------------------

#centec_os_show_process_memory_sorted.textfsm
Value Total (\S+)
Value Used (\S+)

Start
  ^Total: ${Total}; Used: ${Used}; -> Record EOF

------------------------------------------------------------------------------------------------------------------------


#centec_os_show_version.textfsm
Value Version (\S+)
Value uptime (.*)
Value Hostname (\S+)



Start
  ^CentecOS.*Version\s+${Version}
  ^${Hostname}\s+uptime is\s+${uptime} -> Record

------------------------------------------------------------------------------------------------------------------------
#centec_os_show_environment.textfsm
Value List Power (\S+)
Value List Fan (\S+)


Start
  ^FanIndex -> Fan

Fan
  ^\S+\s+${Fan}.*Auto
  ^Power -> Power

Power
  ^\S+\s+\S+\s+${Power}.*NO
  ^\S+\s+\S+\s+${Power}.*ALERT
  ^----- -> Record

index 

Template, Hostname, Platform, Command
centec_os_show_ip_ospf_neighbor_detail.textfsm, .*, centec_os, sh[[ow]] ip o[[spf]] n[[eighbor]] d[[etail]]
centec_os_show_process_memory_sorted.textfsm, .*, centec_os, sh[[ow]] pro[[cess]] m[[emory]] s[[orted]] 
centec_os_show_process_cpu_history.textfsm, .*, centec_os, sh[[ow]] pro[[cess]] cpu h[[istory]]
centec_os_show_interface_status.textfsm, .*, centec_os, sh[[ow]] interface status
centec_os_show_transceiver_detail.textfsm, .*, centec_os, sh[[ow]] tran[[sceiver]] d[[etail]]
centec_os_show_interface_eth.textfsm, .*, centec_os, sh[[ow]] interface eth-0-[[0-54]]
centec_os_show_version.textfsm, .*, centec_os, sh[[ow]] ver[[sion]]
centec_os_show_environment.textfsm, .*, centec_os, sh[[ow]] env[[ironment]]




huawei_vrp_display_interface_xg.textfsm, .*, huawei_vrp, dis[[play]] int[[erface]] X[[GigabitEthernet]]0/0/[[0-44]]
huawei_vrp_display_version.textfsm, .*, huawei_vrp, dis[[play]] ver[[sion]]
huawei_vrp_display_interface_brief.textfsm, .*, huawei_vrp, dis[[play]] int[[erface]] b[[rief]]
huawei_vrp_display_cpu-usage.textfsm, .*, huawei_vrp, dis[[play]] cpu-u[sage]
huawei_vrp_display_memory-usage.textfsm, .*, huawei_vrp, dis[[play]] mem[ory\-usage]
huawei_display_ospf_peer.textfsm, .*, huawei_vrp, dis[[play]] ospf p[eer]
huawei_vrp_display_device.textfsm, .*, huawei_vrp, dis[[play]] dev[[ice]]

计划任务略

Leave a Reply

Your email address will not be published. Required fields are marked *

X