一、前言:在本人看着群里的大佬纷纷分享自己的文章和一些运维思路,深知只有用出来,写出来,分享出来,跟大家一起交流,这个程序和学习到的新知识才算真正掌握了。在这里非常感谢王印王老师 @弈心、朱嘉盛老师 @朱嘉盛以及群里的各位大佬不遗余力地分享着自己的文章。针对python的学习主要源于他们的专栏。 二、实验背景: 前一段时间,机房刚刚建完,但是弱电做的网线不太行,出现两次网线问题,这不是要了命了么。领导说,想办法如果交换机网线出问题了你得能知道。我想说,咱们用个运维平台不行吗,我又想我们今年手里的服务器资源只剩下4核,6g了。
行,那就写个脚本吧。本次实验以h3c设备为基础。
就是说写一个只要接口状态发生变化就能有邮件通知你,而且还有excle作为佐证。
我认为本次实验仅仅适用于十几二十台小环境的网络。(而且有点钱上个平台不比嘛强)
三、需求分析 首先首次使用脚本之后,excle里工作表收集的都是当前所有设备的up的接口。
工作簿底下的工作表的是所有设备,以命名+ip的形式存在。
工作簿底下是所有的设备 然后如果有一个接口的状态出现了变化,只要有变化,link这一列对应的状态就会变色,还会发邮件以工作簿为附件。
如果你是就想人为规划断开这个接口,不是链路的损坏,那么在下次执行这个脚本的时候,不是up接口就不存在了。
如果这个接口从adm状态又回复为up,那么也会发邮件,并且携带工作簿为附件。
最后设置成1分钟执行一次,放在服务器里。
每次执行程序只会维护第一次执行程序输出的这一张表,所以不用担心文件太多。
三、代码分析 3.1完整代码 先上完整代码,然后再进行分析。
# coding=gbkimport reimport smtplibimport threadingimport timefrom email.mime.application import mimeapplicationfrom email.mime.multipart import mimemultipartfrom email.mime.text import mimetextfrom pprint import pprintfrom queue import queuefrom netmiko import connecthandlerfrom openpyxl.reader.excel import load_workbookfrom openpyxl.workbook import workbookfrom openpyxl.styles import patternfill, border, side, fontthreads=[]#用于多线程ip_list=open('ip_file.txt')#ip地址先行放入文件中dims = {}#用于对工作表中自动设置最大行宽content=#用于输出在邮箱中的内容def ssh_seesion(ip, ouput,): global content, sheet # 这几个列表是写入工作表的先行条件,也就是工作表的每一列,先把想写入工作表的每一列的内容写入列表,然后再遍历列表把内容写入工作表 interface_list = [] link_list = [] speed_list = [] description_list = [] connection_info = {'device_type':'hp_comware', 'ip':ip, 'username':'xxxxx', 'password':'xxxxxx'} with connecthandler(**connection_info) as conn: output = conn.send_command(display interface brief,use_textfsm=true) sysname = conn.send_command('display current-configuration | include sysname ') name = re.search(r's+s+s+(s+)', sysname).groups()[0] #pprint(output) try: sheet = wb[name + '_' + ip]#调用自己的那一张表 # 先给逼删了 column_b = sheet['b'] for i in column_b: # print(i.value) if i.value not in ('link', 'up'): num = re.search('d+', i.coordinate).group() # 找到需要删的那一行 print(num) sheet.delete_rows(int(num)) except: pass #取出接口up的 for i in output: if i.get('link')=='up': interface_list.append(i.get('interface')) link_list.append(i.get('link')) speed_list.append(i.get('speed')) description_list.append(i.get('description')) # 判断这次interface跟上次也就是表格里的有没有区别 以是否是up的为前提 不是up的或者多了up的 或者少了up的 只要变化就要被记录 if str(name + '_' + ip) in wb.sheetnames: # 如果这个表存在 就让里原本有的接口进入列表 sheet_pre = wb[name + '_' + ip] column_a = sheet_pre['a']#取出以前的表的第一列 sheet_pre_a1 = [i.value for i in column_a] sheet_pre_a1.remove('interfaces')#遍历第一列的时候会有抬头也就是interfaces' 需要把这个去掉 #print(sheet_pre_a1)#sheet_pre_a1里是上一次表格里有的接口列表 #print(interface_list)#interface_list里是这一次想放入表格里的up的接口的列表 #取出两个列表的差集,这个差集是现在up的和表里的差集 sheet_dif=list(set(sheet_pre_a1)^(set(interface_list)))#把两个表中 变化的接口 放入sheet_dif这个列表里 print(sheet_dif)#至此有变化的且不是up的接口就进入列表了 if len(sheet_dif)!=0:#如果这个列表里有数据就发邮箱 content=content+f{name}的{str(sheet_dif)}接口发生了变化'#配合发邮件的 for i in output:#为了把差集的接口情况写入列表 for p in sheet_dif: if p == i.get('interface') and i.get('link')!='up':#找到这个不是up的接口 各种情况还写进去 interface_list.append(i.get('interface')) link_list.append(i.get('link')) speed_list.append(i.get('speed')) description_list.append(i.get('description')) content=content+i.get('interface')+'接口由up变成了'+i.get('link')+'' elif p == i.get('interface') and i.get('link') == 'up': content = content + i.get('interface') + '接口up了'+'' font = font(name=微软雅黑,bold=true)#字体加粗 yellowfill = patternfill(start_color='ffff00', end_color='ffff00', fill_type='solid')#黄色 thin_border = border(left=side(style='thin'), right=side(style='thin'), top=side(style='thin'), bottom=side(style='thin'))#有边框 springgreen = patternfill(start_color='3cb371', end_color='3cb371', fill_type='solid') # 黄色 if str(name + '_' + ip) in wb.sheetnames:#如果表格存在直接往里写 row_numbers = list(range(2, len(output) + 2)) # 只能从第二行开始 for interface, row in zip(interface_list, row_numbers): sheet.cell(row=row, column=1, value=interface) for link, row in zip(link_list, row_numbers): sheet.cell(row=row, column=2, value=link) for speed, row in zip(speed_list, row_numbers): sheet.cell(row=row, column=3, value=speed) for description, row in zip(description_list, row_numbers): sheet.cell(row=row, column=4, value=description) # 往里写完之后 查看b1这列 然后找到不是up的 给赋值绿色 column_b = sheet['b'] for i in column_b: #print(i.value) if i.value not in ('link','up'): print(i.coordinate)#查找到接口有问题的坐标 sheet[i.coordinate].fill = springgreen else:#如果表格不存在则创建表格 sheet=wb.create_sheet(name+'_'+ip)#这里的sheet相当于jt-6-1f-das-1这个表格 columns=['a1','b1','c1','d1'] cells=['interfaces','link','speed','description'] for i,p in zip(columns,cells): #放入表格中 sheet[i] = p sheet[i].fill=yellowfill sheet[i].font=font row_numbers = list(range(2, len(output) + 2)) # 只能从第二行开始 for interface, row in zip(interface_list, row_numbers): sheet.cell(row=row, column=1, value=interface) for link, row in zip(link_list, row_numbers): sheet.cell(row=row, column=2, value=link) for speed, row in zip(speed_list, row_numbers): sheet.cell(row=row, column=3, value=speed) for description, row in zip(description_list, row_numbers): sheet.cell(row=row, column=4, value=description) for row in sheet.rows: #print(row) for cell in row: #print(cell.value) cell.border = thin_border if cell.value: dims[cell.column_letter] = max((dims.get(cell.column_letter, 0), len(str(cell.value)))) for col, value in dims.items(): sheet.column_dimensions[col].width = value + 3def send_email(sender,receicer,password,content): # 这份代码比较标准了,可以直接用了 # 发件人邮箱 sender = sender # 收件人邮箱 receiver = receicer # 抄送人邮箱 #acc = 'xxxxxxxx@qq.com' # 邮件主题 subject = '服务器运行情况' # 邮箱密码(授权码) password = password # 邮件设置 msg = mimemultipart() msg['subject'] = subject # 主题 msg['to'] = receiver # 接收者 #msg['acc']=acc#抄送者 msg['from'] = 信息化员工 # 发件人 # 邮件正文 content = content # 添加邮件正文: msg.attach(mimetext(content, 'plain', 'utf-8')) # content是正文内容,plain即格式为正文,utf-8是编码格式 # 添加附件 # 注意这里的文件路径是斜杠 file_name = r'e:python est est功能脚本接口up_down est_openpyxl.xlsx' file_name_list = file_name.split('\')[-1] # 获得文件的名字 xlsxpart = mimeapplication(open(file_name, 'rb').read()) xlsxpart.add_header('content-disposition', 'attachment', filename=file_name_list) # 服务端向客户端游览器发送文件时,如果是浏览器支持的文件类型,一般会默认使用浏览器打开,比如txt、jpg等,会直接在浏览器中显示,如果需要提示用户保存,就要利用content-disposition进行一下处理,关键在于一定要加上attachment msg.attach(xlsxpart) # 设置邮箱服务器地址以及端口 smtp_server = smtp.qq.com smtp = smtplib.smtp(smtp_server,25) # 'smtp.qq.com'是qq邮箱发邮件的服务器,用新浪邮箱就是'smtp.sina.com',就是smtp加上你们邮箱账号@符号后面的内容。端口默认是25。 #smtp.set_debuglevel(1) # 显示出交互信息 # 登陆邮箱 smtp.login(sender, password) # 发送邮件 smtp.sendmail(sender, receiver.split(',') , msg.as_string()) # receiver.split(',')+acc.split(',')是['xxxxxxxx@qq.com', 'xxxxxxxx@qq.com'] # 断开服务器链接 smtp.quit()print(f程序于{time.strftime('%x')} 执行开始)#记录开始时间start_time = time.time()#注意逻辑关系 先创建工作簿 再进入多线程 最后保存工作簿try:#如果存在这个表格就直接打开,如果部存在就创建 wb = load_workbook('test_openpyxl.xlsx') ws = wb.activeexcept:# 创建表格 如果存在 就不创建 wb = workbook() wb.remove(wb['sheet']) ws = wb.activefor ips in ip_list.readlines(): t=threading.thread(target=ssh_seesion,args=(ips.strip(),queue())) t.start() threads.append(t)for i in threads: i.join() #加入检查功能if len(content)!=0: print(content) send_email(xxxx@qq.com, xxxx@qq.com, jveyorpbogllijhj,content)end_time = time.time()-start_timewb.save('test_openpyxl.xlsx') # 保存工作表print (f'总共耗时{round(end_time,2)}秒')print(f程序于{time.strftime('%x')} 执行结束) testfsm模板value interface (s+)value link (up|down|adm|stby)value speed (.*g|auto)value description (s+|s+)start ^s*${interface}s+${link}s+${speed}((a)|s*)+s+s+s+s+s+s+s+${description} -> record 3.2分析 特别详细的分析写在了代码的注释中。这里只是对思路的分析。
首先就是登录设备,然后调用testfsm模板做解析,再取出设备的名字。use_textfsm=true的用法参照朱嘉盛:《网络工程师的python之路》(nornir实验10,联动textfsm,ntc-template,华为)
def ssh_seesion(ip, ouput,): global content, sheet # 这几个列表是写入工作表的先行条件,也就是工作表的每一列,先把想写入工作表的每一列的内容写入列表,然后再遍历列表把内容写入工作表 interface_list = [] link_list = [] speed_list = [] description_list = [] connection_info = {'device_type':'hp_comware', 'ip':ip, 'username':'xxxxxx', 'password':'123'} with connecthandler(**connection_info) as conn: output = conn.send_command(display interface brief,use_textfsm=true) sysname = conn.send_command('display current-configuration | include sysname ') name = re.search(r's+s+s+(s+)', sysname).groups()[0] #pprint(output) 首先明确这个脚本是一分钟执行一次,然后是在工作簿中找到此次登录的设备的工作表,然后对其进行删除操作,删除工作表中存在的不是up的接口的那一行,因为这个工作簿的目的是存接口为up的接口的信息,那么之前存在不是up的接口的内个工作簿呢?通过邮箱发出来了。因为如果第一次执行这个程序,那么肯定不存在这个表,所以用个try……except。
i.coordinate用来获取一个格子的坐标的。比如输出结果就是b11这样。
try: sheet = wb[name + '_' + ip]#调用自己的那一张表 # 先给逼删了 column_b = sheet['b'] for i in column_b: # print(i.value) if i.value not in ('link', 'up'): num = re.search('d+', i.coordinate).group() # 找到需要删的那一行 print(num) sheet.delete_rows(int(num)) except: passtry: sheet = wb[name + '_' + ip]#调用自己的那一张表 # 先给逼删了 column_b = sheet['b'] for i in column_b: # print(i.value) if i.value not in ('link', 'up'): num = re.search('d+', i.coordinate).group() # 找到需要删的那一行 print(num) sheet.delete_rows(int(num)) except: passdef ssh_seesion(ip, ouput,): global content, sheet # 这几个列表是写入工作表的先行条件,也就是工作表的每一列,先把想写入工作表的每一列的内容写入列表,然后再遍历列表把内容写入工作表 interface_list = [] link_list = [] speed_list = [] description_list = [] connection_info = {'device_type':'hp_comware', 'ip':ip, 'username':'xxxxxx', 'password':'123'} with connecthandler(**connection_info) as conn: output = conn.send_command(display interface brief,use_textfsm=true) sysname = conn.send_command('display current-configuration | include sysname ') name = re.search(r's+s+s+(s+)', sysname).groups()[0] #pprint(output) 然后取出接口为up的接口的信息,放入表格中,因为这个工作簿的目的是存接口为up的接口的信息。#取出接口up的 for i in output: if i.get('link')=='up': interface_list.append(i.get('interface')) link_list.append(i.get('link')) speed_list.append(i.get('speed')) description_list.append(i.get('description')) # 判断这次interface跟上次也就是表格里的有没有区别 以是否是up的为前提 不是up的或者多了up的 或者少了up的 只要变化就要被记录 再然后就是把表中原有的接口记录在sheet_pre_a1这个例表中,然后与刚才新构成的接口全为up的列表interface_list取差集,差集包含什么?包含可能有新接口up了,可能有旧接口不up了。如果差集中有接口,就写入content中,为了发邮箱用。
然后判断差集里面接口的状态,是又其他状态变为up,还是由up变为了其他状态。还是写如content中,发邮箱用。
if str(name + '_' + ip) in wb.sheetnames: # 如果这个表存在 就让里原本有的接口进入列表 sheet_pre = wb[name + '_' + ip] column_a = sheet_pre['a']#取出以前的表的第一列 sheet_pre_a1 = [i.value for i in column_a] sheet_pre_a1.remove('interfaces')#遍历第一列的时候会有抬头也就是interfaces' 需要把这个去掉 #print(sheet_pre_a1)#sheet_pre_a1里是上一次表格里有的接口列表 #print(interface_list)#interface_list里是这一次想放入表格里的up的接口的列表 #取出两个列表的差集,这个差集是现在up的和表里的差集 sheet_dif=list(set(sheet_pre_a1)^(set(interface_list)))#把两个表中 变化的接口 放入sheet_dif这个列表里 print(sheet_dif)#至此有变化的且不是up的接口就进入列表了 if len(sheet_dif)!=0:#如果这个列表里有数据就发邮箱 content=content+f{name}的{str(sheet_dif)}接口发生了变化'#配合发邮件的 for i in output:#为了把差集的接口情况写入列表 for p in sheet_dif: if p == i.get('interface') and i.get('link')!='up':#找到这个不是up的接口 各种情况还写进去 interface_list.append(i.get('interface')) link_list.append(i.get('link')) speed_list.append(i.get('speed')) description_list.append(i.get('description')) content=content+i.get('interface')+'接口由up变成了'+i.get('link')+'' elif p == i.get('interface') and i.get('link') == 'up': content = content + i.get('interface') + '接口up了'+'' 第一行加粗并且黄色,有状态变化的那一格是绿色。font = font(name=微软雅黑,bold=true)#字体加粗 yellowfill = patternfill(start_color='ffff00', end_color='ffff00', fill_type='solid')#黄色 thin_border = border(left=side(style='thin'), right=side(style='thin'), top=side(style='thin'), bottom=side(style='thin'))#有边框 springgreen = patternfill(start_color='3cb371', end_color='3cb371', fill_type='solid') # 黄色 然后开始往工作表里写东西,如果工作表存在,那么直接写,相当于覆盖。 写完之后查看b1这一列,也就是link这一列,不是up的给赋值绿色。if str(name + '_' + ip) in wb.sheetnames:#如果表格存在直接往里写 #在写之前先删除 # 去表格里找,如果检测到上次接口不是up则把这个接口删掉,不是从python的列表里删掉,直接从表格里删掉 row_numbers = list(range(2, len(output) + 2)) # 只能从第二行开始 for interface, row in zip(interface_list, row_numbers): sheet.cell(row=row, column=1, value=interface) for link, row in zip(link_list, row_numbers): sheet.cell(row=row, column=2, value=link) for speed, row in zip(speed_list, row_numbers): sheet.cell(row=row, column=3, value=speed) for description, row in zip(description_list, row_numbers): sheet.cell(row=row, column=4, value=description) # 往里写完之后 查看b1这列 然后找到不是up的 给赋值绿色 column_b = sheet['b'] for i in column_b: #print(i.value) if i.value not in ('link','up'): print(i.coordinate)#查找到接口有问题的坐标 sheet[i.coordinate].fill = springgreen 如果表格不存在则创建表格再往里写,服务于第一次执行程序else:#如果表格不存在则创建表格 sheet=wb.create_sheet(name+'_'+ip)#这里的sheet相当于jt-6-1f-das-1这个表格 columns=['a1','b1','c1','d1'] cells=['interfaces','link','speed','description'] for i,p in zip(columns,cells): #放入表格中 sheet[i] = p sheet[i].fill=yellowfill sheet[i].font=font row_numbers = list(range(2, len(output) + 2)) # 只能从第二行开始 for interface, row in zip(interface_list, row_numbers): sheet.cell(row=row, column=1, value=interface) for link, row in zip(link_list, row_numbers): sheet.cell(row=row, column=2, value=link) for speed, row in zip(speed_list, row_numbers): sheet.cell(row=row, column=3, value=speed) for description, row in zip(description_list, row_numbers): sheet.cell(row=row, column=4, value=description) 一段以一列中最宽的一格为标准,自动变换列宽的代码 for row in sheet.rows: #print(row) for cell in row: #print(cell.value) cell.border = thin_border if cell.value: dims[cell.column_letter] = max((dims.get(cell.column_letter, 0), len(str(cell.value)))) for col, value in dims.items(): sheet.column_dimensions[col].width = value + 3 然后是发邮件的函数,就不做过多介绍了 def send_email(sender,receicer,password,content): 最后执行主函数
先是创建工作簿,因为可能工作簿已经存在了,所以用try,然后用了多线程快一点,再然后判断content里是否有内容,只要接口发生了状态变化content中就有变化,content有变化就发邮件,邮件附件是工作簿。
print(f程序于{time.strftime('%x')} 执行开始)#记录开始时间start_time = time.time()#注意逻辑关系 先创建工作簿 再进入多线程 最后保存工作簿try:#如果存在这个表格就直接打开,如果部存在就创建 wb = load_workbook('test_openpyxl.xlsx') ws = wb.activeexcept:# 创建表格 如果存在 就不创建 wb = workbook() wb.remove(wb['sheet']) ws = wb.activefor ips in ip_list.readlines(): t=threading.thread(target=ssh_seesion,args=(ips.strip(),queue())) t.start() threads.append(t)for i in threads: i.join() #加入检查功能if len(content)!=0: print(content) send_email(1123824309@qq.com, 1123824309@qq.com, jveyorpbogllijhj,content)end_time = time.time()-start_timewb.save('test_openpyxl.xlsx') # 保存工作表print (f'总共耗时{round(end_time,2)}秒')print(f程序于{time.strftime('%x')} 执行结束) 3.3思路合集 四、测试 首先用四个设备做测试
第一次执行成功输出工作簿,下面的工作表示以名字_ip展现
然后然别断开两个设备的两个接口之后再执行一次程序
如果此时你就是想把这个接口认为donw掉,然后再执行一次程序,也不会有邮件发出,down掉的接口的那一行也被删除了
如果此时接口恢复up,会发邮件通知,而且up的接口也进入到工作表中了。
五、总结 最后把这个脚本仍在服务器里,一分钟执行一次,这样一个低成本的监控交换机接口状态变化的脚本就写完了,其实还有点小问题,比如果接口状态不是up了,在输出工作表时,不是up的那一行就变为了最后一行。
在上大学有一门课叫软件工程,我记得老师教的一句话是”程序开发时要高内聚,低耦合。”然后再看一眼我的代码,真的是有些丑陋,写程序时常常思维不清晰,逻辑不准确。反正,这个脚本在我们现有的网络里能用。
国产比亚迪S9百公里加速不到5秒,比亚迪SUV这性能媲美百万超跑!
多工艺并存,“定制”存储个性化解决方案
陈徐博士Carbon发表关于石墨烯太赫兹超材料器件的最新研究成果
盘点硬件缺陷的问题有哪些和解决方案
电脑的机箱风扇不联网也能泄露机密信息
Netmiko+excle定时检测接口状态
宇凡微Y53R 433MHz合封接收芯片,集成MCU和433接收功能
iOS10.3Beta6时隔3天!iOS10.3Beta7来了苹果你是疯了还是搞事情
手环vs手表,谁将更强?
探究Redis网络模型究竟有多强大(下)
一加5什么时候上市?最新消息:一加5发布会时间确定6.21,第二日发售,现货果然充足!
新型比较器在所有条件下均具有微功耗操作特性
iphone14pro发售 什么时候发售
WDM定时器在冗余技术中的应用
国产大飞机C919距离交付又近一步:再次成功试飞
空间隔离操作系统µC/OS-MPU中段的定位
用C语言实现状态机设计模式
AI时代应该具备怎样的嵌入式思维
西门子MES平台在湖北卷烟厂中的应用
自制万用表升压电路(三款万用表升压电路设计方案详解)