电子说
一、前言:在本人看着群里的大佬纷纷分享自己的文章和一些运维思路,深知只有用出来,写出来,分享出来,跟大家一起交流,这个程序和学习到的新知识才算真正掌握了。在这里非常感谢王印王老师 @弈心、朱嘉盛老师 @朱嘉盛以及群里的各位大佬不遗余力地分享着自己的文章。针对python的学习主要源于他们的专栏。
前一段时间,机房刚刚建完,但是弱电做的网线不太行,出现两次网线问题,这不是要了命了么。领导说,想办法如果交换机网线出问题了你得能知道。我想说,咱们用个运维平台不行吗,我又想我们今年手里的服务器资源只剩下4核,6G了。
行,那就写个脚本吧。本次实验以H3C设备为基础。
就是说写一个只要接口状态发生变化就能有邮件通知你,而且还有excle作为佐证。
我认为本次实验仅仅适用于十几二十台小环境的网络。(而且有点钱上个平台不比嘛强)
首先首次使用脚本之后,excle里工作表收集的都是当前所有设备的UP的接口。
工作簿底下的工作表的是所有设备,以命名+IP的形式存在。
工作簿底下是所有的设备
然后如果有一个接口的状态出现了变化,只要有变化,Link这一列对应的状态就会变色,还会发邮件以工作簿为附件。
如果你是就想人为规划断开这个接口,不是链路的损坏,那么在下次执行这个脚本的时候,不是UP接口就不存在了。
如果这个接口从ADM状态又回复为UP,那么也会发邮件,并且携带工作簿为附件。
最后设置成1分钟执行一次,放在服务器里。
每次执行程序只会维护第一次执行程序输出的这一张表,所以不用担心文件太多。
先上完整代码,然后再进行分析。
# coding=gbk
import re
import smtplib
import threading
import time
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from pprint import pprint
from queue import Queue
from netmiko import ConnectHandler
from openpyxl.reader.excel import load_workbook
from openpyxl.workbook import Workbook
from openpyxl.styles import PatternFill, Border, Side, Font
threads=[]#用于多线程
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 + 3
def 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.active
except:# 创建表格 如果存在 就不创建
wb = Workbook()
wb.remove(wb['Sheet'])
ws = wb.active
for 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_time
wb.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
特别详细的分析写在了代码的注释中。这里只是对思路的分析。
首先就是登录设备,然后调用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:
pass
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的
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') # 黄色
然后开始往工作表里写东西,如果工作表存在,那么直接写,相当于覆盖。
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.active
except:# 创建表格 如果存在 就不创建
wb = Workbook()
wb.remove(wb['Sheet'])
ws = wb.active
for 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_time
wb.save('test_openpyxl.xlsx') # 保存工作表
print (f'总共耗时{round(end_time,2)}秒')
print(f"程序于{time.strftime('%X')} 执行结束
")
首先用四个设备做测试
第一次执行成功输出工作簿,下面的工作表示以名字_ip展现
然后然别断开两个设备的两个接口之后再执行一次程序
如果此时你就是想把这个接口认为donw掉,然后再执行一次程序,也不会有邮件发出,down掉的接口的那一行也被删除了
如果此时接口恢复UP,会发邮件通知,而且UP的接口也进入到工作表中了。
最后把这个脚本仍在服务器里,一分钟执行一次,这样一个低成本的监控交换机接口状态变化的脚本就写完了,其实还有点小问题,比如果接口状态不是up了,在输出工作表时,不是UP的那一行就变为了最后一行。
在上大学有一门课叫软件工程,我记得老师教的一句话是”程序开发时要高内聚,低耦合。”然后再看一眼我的代码,真的是有些丑陋,写程序时常常思维不清晰,逻辑不准确。反正,这个脚本在我们现有的网络里能用。
审核编辑 :李倩
全部0条评论
快来发表一下你的评论吧 !