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]]
计划任务略