一、前言:在本人看著群里的大佬紛紛分享自己的文章和一些運維思路,深知只有用出來,寫出來,分享出來,跟大家一起交流,這個程序和學習到的新知識才算真正掌握了。在這里非常感謝王印王老師@弈心、朱嘉盛老師@朱嘉盛以及群里的各位大佬不遺余力地分享著自己的文章。針對python的學習主要源于他們的專欄。
二、實驗背景:
前一段時間,機房剛剛建完,但是弱電做的網線不太行,出現兩次網線問題,這不是要了命了么。領導說,想辦法如果交換機網線出問題了你得能知道。我想說,咱們用個運維平臺不行嗎,我又想我們今年手里的服務器資源只剩下4核,6G了。
行,那就寫個腳本吧。本次實驗以H3C設備為基礎。
就是說寫一個只要接口狀態發生變化就能有郵件通知你,而且還有excle作為佐證。
我認為本次實驗僅僅適用于十幾二十臺小環境的網絡。(而且有點錢上個平臺不比嘛強)
三、需求分析
首先首次使用腳本之后,excle里工作表收集的都是當前所有設備的UP的接口。
工作簿底下的工作表的是所有設備,以命名+IP的形式存在。
工作簿底下是所有的設備
然后如果有一個接口的狀態出現了變化,只要有變化,Link這一列對應的狀態就會變色,還會發郵件以工作簿為附件。
如果你是就想人為規劃斷開這個接口,不是鏈路的損壞,那么在下次執行這個腳本的時候,不是UP接口就不存在了。
如果這個接口從ADM狀態又回復為UP,那么也會發郵件,并且攜帶工作簿為附件。
最后設置成1分鐘執行一次,放在服務器里。
每次執行程序只會維護第一次執行程序輸出的這一張表,所以不用擔心文件太多。
三、代碼分析
3.1完整代碼
先上完整代碼,然后再進行分析。
#coding=gbk
importre
importsmtplib
importthreading
importtime
fromemail.mime.applicationimportMIMEApplication
fromemail.mime.multipartimportMIMEMultipart
fromemail.mime.textimportMIMEText
frompprintimportpprint
fromqueueimportQueue
fromnetmikoimportConnectHandler
fromopenpyxl.reader.excelimportload_workbook
fromopenpyxl.workbookimportWorkbook
fromopenpyxl.stylesimportPatternFill,Border,Side,Font
threads=[]#用于多線程
ip_list=open('ip_file.txt')#IP地址先行放入文件中
dims={}#用于對工作表中自動設置最大行寬
content=""#用于輸出在郵箱中的內容
defssh_seesion(ip,ouput,):
globalcontent,sheet
#這幾個列表是寫入工作表的先行條件,也就是工作表的每一列,先把想寫入工作表的每一列的內容寫入列表,然后再遍歷列表把內容寫入工作表
interface_list=[]
link_list=[]
speed_list=[]
description_list=[]
connection_info={'device_type':'hp_comware',
'ip':ip,
'username':'xxxxx',
'password':'xxxxxx'}
withConnectHandler(**connection_info)asconn:
output=conn.send_command("displayinterfacebrief",use_textfsm=True)
sysname=conn.send_command('displaycurrent-configuration|includesysname')
name=re.search(r's+S+s+(S+)',sysname).groups()[0]
#pprint(output)
try:
sheet=wb[name+'_'+ip]#調用自己的那一張表
#先給逼刪了
column_B=sheet['B']
foriincolumn_B:
#print(i.value)
ifi.valuenotin('Link','UP'):
num=re.search('d+',i.coordinate).group()#找到需要刪的那一行
print(num)
sheet.delete_rows(int(num))
except:
pass
#取出接口up的
foriinoutput:
ifi.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的只要變化就要被記錄
ifstr(name+'_'+ip)inwb.sheetnames:#如果這個表存在就讓里原本有的接口進入列表
sheet_pre=wb[name+'_'+ip]
column_A=sheet_pre['A']#取出以前的表的第一列
sheet_pre_A1=[i.valueforiincolumn_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的接口就進入列表了
iflen(sheet_dif)!=0:#如果這個列表里有數據就發郵箱
content=content+f"{name}的{str(sheet_dif)}接口發生了變化
'"#配合發郵件的
foriinoutput:#為了把差集的接口情況寫入列表
forpinsheet_dif:
ifp==i.get('interface')andi.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')+'
'
elifp==i.get('interface')andi.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')#黃色
ifstr(name+'_'+ip)inwb.sheetnames:#如果表格存在直接往里寫
row_numbers=list(range(2,len(output)+2))#只能從第二行開始
forinterface,rowinzip(interface_list,row_numbers):
sheet.cell(row=row,column=1,value=interface)
forlink,rowinzip(link_list,row_numbers):
sheet.cell(row=row,column=2,value=link)
forspeed,rowinzip(speed_list,row_numbers):
sheet.cell(row=row,column=3,value=speed)
fordescription,rowinzip(description_list,row_numbers):
sheet.cell(row=row,column=4,value=description)
#往里寫完之后查看B1這列然后找到不是UP的給賦值綠色
column_B=sheet['B']
foriincolumn_B:
#print(i.value)
ifi.valuenotin('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']
fori,pinzip(columns,cells):
#放入表格中
sheet[i]=p
sheet[i].fill=yellowFill
sheet[i].font=font
row_numbers=list(range(2,len(output)+2))#只能從第二行開始
forinterface,rowinzip(interface_list,row_numbers):
sheet.cell(row=row,column=1,value=interface)
forlink,rowinzip(link_list,row_numbers):
sheet.cell(row=row,column=2,value=link)
forspeed,rowinzip(speed_list,row_numbers):
sheet.cell(row=row,column=3,value=speed)
fordescription,rowinzip(description_list,row_numbers):
sheet.cell(row=row,column=4,value=description)
forrowinsheet.rows:
#print(row)
forcellinrow:
#print(cell.value)
cell.border=thin_border
ifcell.value:
dims[cell.column_letter]=max((dims.get(cell.column_letter,0),len(str(cell.value))))
forcol,valueindims.items():
sheet.column_dimensions[col].width=value+3
defsend_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
foripsinip_list.readlines():
t=threading.Thread(target=ssh_seesion,args=(ips.strip(),Queue()))
t.start()
threads.append(t)
foriinthreads:
i.join()
#加入檢查功能
iflen(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模板
ValueInterface(S+)
ValueLink(UP|DOWN|ADM|Stby)
ValueSpeed(.*G|auto)
ValueDescription(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,華為)
defssh_seesion(ip,ouput,):
globalcontent,sheet
#這幾個列表是寫入工作表的先行條件,也就是工作表的每一列,先把想寫入工作表的每一列的內容寫入列表,然后再遍歷列表把內容寫入工作表
interface_list=[]
link_list=[]
speed_list=[]
description_list=[]
connection_info={'device_type':'hp_comware',
'ip':ip,
'username':'xxxxxx',
'password':'123'}
withConnectHandler(**connection_info)asconn:
output=conn.send_command("displayinterfacebrief",use_textfsm=True)
sysname=conn.send_command('displaycurrent-configuration|includesysname')
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']
foriincolumn_B:
#print(i.value)
ifi.valuenotin('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']
foriincolumn_B:
#print(i.value)
ifi.valuenotin('Link','UP'):
num=re.search('d+',i.coordinate).group()#找到需要刪的那一行
print(num)
sheet.delete_rows(int(num))
except:
pass
defssh_seesion(ip,ouput,):
globalcontent,sheet
#這幾個列表是寫入工作表的先行條件,也就是工作表的每一列,先把想寫入工作表的每一列的內容寫入列表,然后再遍歷列表把內容寫入工作表
interface_list=[]
link_list=[]
speed_list=[]
description_list=[]
connection_info={'device_type':'hp_comware',
'ip':ip,
'username':'xxxxxx',
'password':'123'}
withConnectHandler(**connection_info)asconn:
output=conn.send_command("displayinterfacebrief",use_textfsm=True)
sysname=conn.send_command('displaycurrent-configuration|includesysname')
name=re.search(r's+S+s+(S+)',sysname).groups()[0]
#pprint(output)
然后取出接口為UP的接口的信息,放入表格中,因為這個工作簿的目的是存接口為UP的接口的信息。
#取出接口up的
foriinoutput:
ifi.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中,發郵箱用。
ifstr(name+'_'+ip)inwb.sheetnames:#如果這個表存在就讓里原本有的接口進入列表
sheet_pre=wb[name+'_'+ip]
column_A=sheet_pre['A']#取出以前的表的第一列
sheet_pre_A1=[i.valueforiincolumn_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的接口就進入列表了
iflen(sheet_dif)!=0:#如果這個列表里有數據就發郵箱
content=content+f"{name}的{str(sheet_dif)}接口發生了變化
'"#配合發郵件的
foriinoutput:#為了把差集的接口情況寫入列表
forpinsheet_dif:
ifp==i.get('interface')andi.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')+'
'
elifp==i.get('interface')andi.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的給賦值綠色。
ifstr(name+'_'+ip)inwb.sheetnames:#如果表格存在直接往里寫
#在寫之前先刪除#去表格里找,如果檢測到上次接口不是up則把這個接口刪掉,不是從python的列表里刪掉,直接從表格里刪掉
row_numbers=list(range(2,len(output)+2))#只能從第二行開始
forinterface,rowinzip(interface_list,row_numbers):
sheet.cell(row=row,column=1,value=interface)
forlink,rowinzip(link_list,row_numbers):
sheet.cell(row=row,column=2,value=link)
forspeed,rowinzip(speed_list,row_numbers):
sheet.cell(row=row,column=3,value=speed)
fordescription,rowinzip(description_list,row_numbers):
sheet.cell(row=row,column=4,value=description)
#往里寫完之后查看B1這列然后找到不是UP的給賦值綠色
column_B=sheet['B']
foriincolumn_B:
#print(i.value)
ifi.valuenotin('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']
fori,pinzip(columns,cells):
#放入表格中
sheet[i]=p
sheet[i].fill=yellowFill
sheet[i].font=font
row_numbers=list(range(2,len(output)+2))#只能從第二行開始
forinterface,rowinzip(interface_list,row_numbers):
sheet.cell(row=row,column=1,value=interface)
forlink,rowinzip(link_list,row_numbers):
sheet.cell(row=row,column=2,value=link)
forspeed,rowinzip(speed_list,row_numbers):
sheet.cell(row=row,column=3,value=speed)
fordescription,rowinzip(description_list,row_numbers):
sheet.cell(row=row,column=4,value=description)
一段以一列中最寬的一格為標準,自動變換列寬的代碼
forrowinsheet.rows:
#print(row)
forcellinrow:
#print(cell.value)
cell.border=thin_border
ifcell.value:
dims[cell.column_letter]=max((dims.get(cell.column_letter,0),len(str(cell.value))))
forcol,valueindims.items():
sheet.column_dimensions[col].width=value+3
然后是發郵件的函數,就不做過多介紹了
defsend_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
foripsinip_list.readlines():
t=threading.Thread(target=ssh_seesion,args=(ips.strip(),Queue()))
t.start()
threads.append(t)
foriinthreads:
i.join()
#加入檢查功能
iflen(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')}執行結束
")
3.3思路合集
四、測試
首先用四個設備做測試
第一次執行成功輸出工作簿,下面的工作表示以名字_ip展現
然后然別斷開兩個設備的兩個接口之后再執行一次程序
如果此時你就是想把這個接口認為donw掉,然后再執行一次程序,也不會有郵件發出,down掉的接口的那一行也被刪除了
如果此時接口恢復UP,會發郵件通知,而且UP的接口也進入到工作表中了。
五、總結
最后把這個腳本仍在服務器里,一分鐘執行一次,這樣一個低成本的監控交換機接口狀態變化的腳本就寫完了,其實還有點小問題,比如果接口狀態不是up了,在輸出工作表時,不是UP的那一行就變為了最后一行。
在上大學有一門課叫軟件工程,我記得老師教的一句話是”程序開發時要高內聚,低耦合。”然后再看一眼我的代碼,真的是有些丑陋,寫程序時常常思維不清晰,邏輯不準確。反正,這個腳本在我們現有的網絡里能用。
審核編輯 :李倩
-
ADM
+關注
關注
0文章
30瀏覽量
16030 -
弱電
+關注
關注
0文章
79瀏覽量
14762 -
python
+關注
關注
56文章
4799瀏覽量
84820
原文標題:網工Python之路之Netmiko+excle定時檢測接口狀態
文章出處:【微信號:釋然IT雜談,微信公眾號:釋然IT雜談】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論