通過幾天折騰,終於搞出來了一個python網絡設備配置備份的腳本。
基本思路以下:
1.定義設備類Device
1.1初始化參數dc, zone, hostname, ip, user, passwd(數據中心,網絡區域,設備名稱,ip,用戶名,密碼)
1.2函數login設備登陸
1.3函數screenCMD設備測試是否支持分屏命令(目前只有華爲的作這個測試)
1.4函數get_config支持分屏命令的設備取配置方法
1.5函數get_config_more不支持分屏的設備取配置方法
2.判斷設備廠家返回設備函數DeviceVendor
3.定義各個設備的命令函數
4.定義讀寫文件函數FileOps
5.定義時間函數TimeOps
6.主函數,循環讀取設備列表,根據設備廠家執行函數與記錄打印日誌,存檔配置文件python
有優化建議請大神提一提哈!!!shell
#!/usr/bin/env python3 # -*- coding: utf-8 -*- ''' 使用說明: 1.設備列表爲csv格式(文本),第一行爲註釋,執行時會自動跳過第一行 數據中心,網絡分區,設備名稱,設備IP,用戶名,密碼,廠商 2.主函數中的參數即爲該設備列表的名稱 3.日誌:3.1實時打印日誌 3.2詳細日誌和結果日誌會生成在腳本目錄log 4.配置備份目錄,自動生成,目錄爲YMD網絡配置備份,YMD分別爲年月日,二三級目錄根據數據中心,網絡分區生產 當前版本1.3 版本更新說明 初始版本:支持華爲display cu,測試screen-length 0 temporary 1.0更新,將各廠商設備方法get_config和get_config_more轉成Device類的通用方法 1.1更新:1.locale在不一樣的操做系統可能報錯,時間顯示修改爲默認, 2.各提示修改成英文,讀寫編碼修改成GBK,解決某些設備寫入錯誤問題。 3.解決paramiko recv 65535問題,經過while語句,config+=outbuf.decode 4.寫入配置前替換不支持分屏命令的設備記錄日誌時有 ---- More ----[42D [42D 字符 5.增長了異常拋出登陸時ssh版本問題Incompatible version 5.增長異常拋出,設備型號不支持記錄並繼續下一步class ErrorType(Device) 1.1.1更新:提示爲中文 1.2更新:1.配置記錄不全的設備嘗試從新登陸備份 1.3更新:1.增長執行詳細記錄log和執行結果log 2.增長詳細日誌和結果日誌裏的password顯示爲****** 3.將devicelist獨立爲一個參數,放到main函數上 4.4.F5不分屏modify cli preference pager disabled display-threshold 0 ''' import socket, os, time, re import paramiko #定義設備類 class Device(object): # 初始化參數,與設備devicelist的列參數一一對應 #定義通用方法,login登陸,screenCMD判斷是否支持分屏命令, # get_config通用分屏命令取配置,get_config_more爲不支持分屏命令的取配置方法 def __init__(self, dc, zone, hostname, ip, user, passwd): self.dc=dc self.zone=zone self.hostname = hostname self.ip = ip self.user = user self.passwd = passwd self.success_flag = True self.screen_cmd_flag = True self.success_login_count = 0#登陸成功次數 self.success_backup_count = 0#備份成功次數 self.fail_backup_count = 0#備份失敗次數 self.fail_login_count = 0#登錄失敗次數 self.fail_backup_context = ''#存放備份失敗的設備信息 self.fail_login_context = ''#存放登陸失敗的設備信息 self.exe_detail_log=''# 保存執行過程詳細日誌 self.ssh= paramiko.SSHClient() self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #登陸 def login(self): if self.success_flag: try: self.ssh.connect(hostname=self.ip, username=self.user, password=self.passwd, look_for_keys=False, timeout=5) self.success_login_count = 1 except paramiko.ssh_exception.AuthenticationException as e: self.success_flag = False self.fail_login_count = 1 self.fail_login_context='%s-%s - %s' % (self.hostname,self.ip, e) self.exe_detail_log+=(TimeOps.get_time_stamp()+','+self.fail_login_context+'\n') print(self.fail_login_context) except paramiko.ssh_exception.SSHException as e: #paramiko.ssh_exception.SSHException: Incompatible version (1.5 instead of 2.0) self.success_flag = False self.fail_login_count = 1 self.fail_login_context='%s-%s - %s' % (self.hostname,self.ip, e) self.exe_detail_log+=(TimeOps.get_time_stamp()+','+self.fail_login_context+'\n') print(self.fail_login_context) except socket.timeout as e: self.success_flag = False self.fail_login_count = 1 self.fail_login_context='%s-%s - %s' % (self.hostname,self.ip, e) self.exe_detail_log+=(TimeOps.get_time_stamp()+','+self.fail_login_context+'\n') print(self.fail_login_context) else: pass #判斷是否支持分屏 def screenCMD(self,scmd): Device.login(self) if self.success_flag : cmd = self.ssh.invoke_shell() cmd.send(scmd+'\n') time.sleep(1) out_buf = cmd.recv(65535).decode('gbk', 'ignore') self.ssh.close() if re.findall(r'Error:|Unrecognized command|Incomplete command|Wrong parameter', out_buf): self.screen_cmd_flag = False self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + '%s-%s設備不支持分屏disable命令'%(self.hostname,self.ip)+'\n') #支持分屏的獲取命令,並將日誌寫入爲文件 def get_config(self,log_return,*args):#log_return爲配置完成的返回值(好比return),*args爲命令集list recv_config=''#str類型 start_time=time.time() Device.login(self)#登陸設備 if self.success_flag : success_log='%s-%s 登陸成功。'%(self.hostname,self.ip) print(success_log,end='') self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + success_log+'\n') cmd=self.ssh.invoke_shell() for i in args: # 循環讀取命令集,並逐一執行 time.sleep(0.3) cmd.send(i + "\n") time.sleep(3) # 測試3s能完整記錄,有些設備登陸比較慢(H3C),在下一環節進行時間等待if 'return' not in str(out_buf) while True: # 持續接受通道的數據流,解決recv 65535問題 if cmd.recv_ready(): out_buf = cmd.recv(65535) # byte類型 if len(out_buf) == 0: raise EOFError('通道數據流被遠程設備關閉。') recv_config += out_buf.decode('gbk', 'ignore') if log_return not in str(out_buf):#若是記錄未完成,繼續等待記錄 time.sleep(0.3) else: break if log_return in recv_config: self.success_backup_count = 1 success_log = '日誌數據流記錄成功。' print(success_log, end='') self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + success_log+'\n') else: self.fail_backup_count = 1 self.fail_backup_context='%s日誌中沒有包含關鍵字 %s 。'%(self.ip,log_return) self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + self.fail_backup_context+'\n') print('%s日誌中沒有包含關鍵字 \033[0;31m%s\033[0m 。'%(self.ip,log_return),end='') self.ssh.close() recv_config = recv_config.replace('\r\n', '\n') # Windows顯示\r\n分行替換爲\n FileOps.writecfg(self.dc, self.zone, self.hostname, recv_config)#將日誌寫到文件 end_time=time.time() time_log='耗時 %s 秒。'%int(end_time-start_time) self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + time_log+'\n') print(time_log) # 不支持分屏的獲取命令,並將日誌寫入爲文件 def get_config_more(self,log_return,*args):#接受命令集 recv_config=''#str類型 start_time=time.time() Device.login(self)#設備登陸 if self.success_flag : success_log='%s-%s 登陸成功。'%(self.hostname,self.ip) print(success_log,end='') self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + success_log+'\n') cmd=self.ssh.invoke_shell() for i in args:#循環讀取命令集,並逐一執行 time.sleep(0.3) cmd.send(i+"\n") while True:#持續接受通道的數據流,解決recv 65535問題 if cmd.recv_ready(): out_buf=cmd.recv(65535)#byte類型 if len(out_buf)==0: raise EOFError('通道數據流被遠程設備關閉。') recv_config +=out_buf.decode('gbk', 'ignore') if log_return not in str(out_buf) and str(out_buf).endswith('--')==False:#可能有多種狀況的more--,取-- cmd.send(' ') time.sleep(0.1) else: break if log_return in recv_config: self.success_backup_count = 1 success_log = '日誌數據流記錄成功。' print(success_log, end='') self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + success_log+'\n') else: self.fail_backup_count = 1 self.fail_backup_context='%s日誌中沒有包含關鍵字 %s 。'%(self.ip,log_return) self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + self.fail_backup_context+'\n') print('%s日誌中沒有包含關鍵字 \033[0;31m%s\033[0m 。'%(self.ip,log_return),end='') self.ssh.close() recv_config = recv_config.replace('\r\n','\n')#Windows顯示\r\n分行替換爲\n self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + '執行替換\\r\\n爲\\n動做。'+'\n') recv_config = recv_config.replace(' ---- More ----[42D [42D','') recv_config = recv_config.replace(' ---- More ----[42D [42D ','') self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + '執行替換---- More ----動做。'+'\n') #解決More的顯示問題 FileOps.writecfg(self.dc, self.zone, self.hostname, recv_config) end_time=time.time() time_log='耗時 %s 秒。'%int(end_time-start_time) self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + time_log+'\n') print(time_log) #華爲 class Huawei(Device): #華爲須要判斷是否支持分屏 def ops(self): #測試是否支持分屏命令 Huawei.screenCMD(self,'screen-length 0 temporary') if self.success_flag: if self.screen_cmd_flag: Huawei.get_config(self,'return','screen-length 0 temporary','display cu') # 第一個爲記錄配置完成標誌的返回值,後面爲命令集 else: Huawei.get_config_more(self,'return','display cu') # 第一個爲記錄配置完成標誌的返回值,後面爲命令集 class H3C(Device): def ops(self): if self.success_flag: H3C.get_config(self,'return','screen-length disable','display cu') # 第一個爲記錄配置完成標誌的返回值,後面爲命令集 class Cisco(Device): def ops(self): if self.success_flag: Cisco.get_config(self,'line vty','terminal length 0','show run') # 第一個爲記錄配置完成標誌的返回值,後面爲命令集 class ZTE(Device): def ops(self): if self.success_flag: ZTE.get_config(self,'end','terminal length 0','show run') # 第一個爲記錄配置完成標誌的返回值,後面爲命令集 class A10(Device): def ops(self): if self.success_flag: A10.get_config(self,'end','enable\n','terminal length 0','show running-config all-partitions') # 第一個爲記錄配置完成標誌的返回值,後面爲命令集 class FiberHome(Device): def ops(self): if self.success_flag: FiberHome.get_config(self,'#','enable','terminal length 0','show running-config') # 第一個爲記錄配置完成標誌的返回值,後面爲命令集 class F5(Device): def ops(self): if self.success_flag: F5.get_config(self,'(tmos)#','tmsh','modify cli preference pager disabled display-threshold 0','show running-config') class TOPSEC(Device): def ops(self): if self.success_flag: TOPSEC.get_config(self,'config implement','show-running nostop') class Forti(Device): def ops(self): if self.success_flag: Forti.get_config(self,'router setting','config system console','set output standard','end','show full-configuration') class Maipu(Device): def ops(self): if self.success_flag: Maipu.get_config(self,'end','more off','show running-config') class ErrorType(Device): def ops(self): self.fail_login_count = 1 self.fail_login_context = '不支持 %s-%s 該設備型號。' % (self.hostname, self.ip) self.exe_detail_log += (TimeOps.get_time_stamp() + ',' + self.fail_login_context+'\n') print('不支持 \033[0;31m%s-%s\033[0m 該設備型號。' % (self.hostname, self.ip)) #定義文件操做,包括讀取設備列表文件,設備配置存檔 class FileOps(object): #文件讀取 cur_path = os.getcwd() write_main_path = cur_path + ('\\%s網絡配置備份' % time.strftime('%Y%m%d'))#備份的主目錄 @staticmethod def readcfg(file): with open(file, 'r', encoding='GBK') as f: filelist = f.read().split('\n') filelist.pop(0)#配置文件首行爲註釋行 return filelist # 文件保存 @staticmethod def writecfg(dc,zone,hostname,cfg): dev_path='\\%s\\%s'%(dc,zone)#設備子目錄,根據設備數據中心/網絡分區劃分 write_path=FileOps.write_main_path+dev_path#寫入的詳細目錄 if not os.path.exists(write_path): os.makedirs(write_path) file='%s.txt'%(hostname) with open(write_path+'\\'+file, 'w',encoding='gbk') as f: f.write(cfg) print('配置備份成功,保存爲 %s\%s' % (dev_path,file),end='。')#只顯示設備子目錄 #日誌寫入 @staticmethod def writelog(logstr,logname): logpath='log' log_separator='分 隔 行'.center(60,'-') if not os.path.exists(logpath): os.makedirs(logpath) logname=logname+'%s.log'%time.strftime('%Y%m%d') with open(logpath+'\\'+logname, 'a',encoding='gbk') as f: f.write('\n'+log_separator+'\n'+logstr) #設備廠商判斷,返回設備廠商函數,自動實例化 class DeviceVendor(object): # 判斷設備廠商,自動實例化 @staticmethod def OPS(*args):#任意列表參數 if str(args[6]).upper() == 'HUAWEI': return Huawei(*args[0:-1]) elif str(args[6]).upper() == 'H3C': return H3C(*args[0:-1]) elif str(args[6]).upper() == 'CISCO': return Cisco(*args[0:-1]) elif str(args[6]).upper() == 'ZTE': return ZTE(*args[0:-1]) elif str(args[6]).upper() == 'A10': return A10(*args[0:-1]) elif str(args[6]).upper() == 'FIBERHOME': return FiberHome(*args[0:-1]) elif str(args[6]).upper() == 'F5': return F5(*args[0:-1]) elif str(args[6]).upper() == 'TOPSEC': return TOPSEC(*args[0:-1]) elif str(args[6]).upper() == 'FORTI': return Forti(*args[0:-1]) elif str(args[6]).upper() == 'MAIPU': return Maipu(*args[0:-1]) else: return ErrorType(*args[0:-1])#不支持的設備類型 class TimeOps(object):#計算時間戳到毫秒 @staticmethod def get_time_stamp(): ct = time.time() local_time = time.localtime(ct) data_head = time.strftime("%Y-%m-%d %H:%M:%S", local_time) data_secs = (ct - int(ct)) * 1000 time_stamp = "%s.%03d" % (data_head, data_secs) return time_stamp def main(devicelist): all_exe_detail_log=''#運行詳細日誌 all_exe_result_log=''#運行結果日誌 start_time = time.time() start_log='%s,配置備份程序正在運行,請等待!!!'%time.strftime('%Y/%m/%d %H:%M:%S')+\ '\n'+'備份路徑爲 : %s'%FileOps.write_main_path all_exe_detail_log += (TimeOps.get_time_stamp() + ',' + start_log+'\n') print(start_log) devicelist=FileOps.readcfg(devicelist)#讀取設備列表,這裏已轉爲list[] all_success_login_count = 0 # 登陸成功次數 all_success_backup_count = 0 # 備份成功次數 all_fail_backup_count = 0 # 備份失敗次數 all_fail_login_count = 0 # 登錄失敗次數 devicelistErr_count=0 all_fail_backup_context = [] # 存放備份失敗的設備信息 all_fail_login_context = [] # 存放登陸失敗的設備信息 devicelistErr_context=[] # 存放設備list參數錯誤的設備信息 all_fail_backup_dev_dict={} # 存放設備備份記錄不全的設備列表,用於下次嘗試繼續登陸備份 devicenum=0#統計總的設備數量 for i in devicelist:#循環設備列表[]的每行 list=i.split(',')#將每行的str轉換爲list[] if len(list)==7:#斷定該行的參數是否完整,可能出現空行,須要跳過 devicenum+=1 DeviceOps=DeviceVendor().OPS(*list)#判斷設備廠商,執行設備廠商class,參數以list傳入 DeviceOps.ops()#調用廠商方法 all_success_login_count+=DeviceOps.success_login_count#記錄登陸成功的次數 all_success_backup_count+=DeviceOps.success_backup_count#記錄備份成功的次數 all_fail_login_count+=DeviceOps.fail_login_count#記錄登陸失敗的次數 all_fail_backup_count+=DeviceOps.fail_backup_count#記錄備份失敗的次數 if DeviceOps.fail_login_context != '': all_fail_login_context.append(DeviceOps.fail_login_context)#記錄登陸失敗的日誌 if DeviceOps.fail_backup_context != '': all_fail_backup_context.append(DeviceOps.fail_backup_context)#記錄備份失敗的日誌 fail_backup_index=all_fail_backup_context.index(DeviceOps.fail_backup_context)#該設備失敗日誌在總的失敗日誌裏的index all_fail_backup_dev_dict[fail_backup_index]=i #將設備信息加入到備份失敗字典,用於下次繼續嘗試登陸備份,key爲敗日誌裏的index(方便後續刪除),value爲設備參數信息 all_exe_detail_log += DeviceOps.exe_detail_log # 記錄運行詳細日誌 else:#不然記錄錯誤設備參數並打印 devicenum+=1 devicelistErr_count+=1 if len(list)>5: list[5]='******'#將密碼替換成****** devicelistErr='Devicelist第 %s 行參數不對或者空行,行信息以下: %s'%(devicelist.index(i)+2,','.join(list)) #devicelist.index(i)+2,list從0開始,且pop了第一行註釋,所以須要+2 devicelistErr_context.append(devicelistErr) all_exe_detail_log += (TimeOps.get_time_stamp()+','+devicelistErr) # 記錄運行詳細日誌 print('Devicelist第 \033[0;31m%s\033[0m 行參數不對或者空行,行信息以下: %s'%(devicelist.index(i)+2,','.join(list))) #嘗試從新登陸備份 if all_fail_backup_dev_dict!={}: re_backup_log='\n嘗試從新登陸備份失敗的設備!' print('\n\033[1m嘗試從新登陸備份失敗的設備!\033[0m') all_exe_detail_log += (TimeOps.get_time_stamp()+','+re_backup_log+'\n') # 記錄運行詳細日誌 for k,v in all_fail_backup_dev_dict.items(): # 遍歷字典,k爲all_fail_backup_context的index,v爲設備參數 list = v.split(',') # 將每行的str轉換爲list[] DeviceOps = DeviceVendor().OPS(*list) # 判斷設備廠商,執行設備廠商class,參數以list傳入 DeviceOps.ops() # 調用廠商方法 if DeviceOps.fail_backup_context != '':#備份失敗 re_backup_failed_log='從新備份失敗,%s'%DeviceOps.fail_backup_context all_exe_detail_log += (TimeOps.get_time_stamp() +','+ re_backup_failed_log+'\n') print(re_backup_failed_log) else: all_success_backup_count += DeviceOps.success_backup_count # 增長備份成功的次數 all_fail_backup_count -= DeviceOps.success_backup_count # 減小備份失敗的次數減 all_fail_backup_context.pop(k)#刪除備份失敗的內容 re_backup_success_log='%s-%s從新備份成功'%(DeviceOps.hostname,DeviceOps.ip) all_exe_detail_log += (TimeOps.get_time_stamp() +','+ re_backup_success_log) print(re_backup_success_log) #結果信息 result_log='\n#*#*#*#*備 份 結 果*#*#*#*#' all_exe_detail_log+=(TimeOps.get_time_stamp()+','+result_log+'\n') all_exe_result_log+=(TimeOps.get_time_stamp()+'\n'+result_log+'\n') print('\n\033[1m#*#*#*#*備 份 結 果*#*#*#*#\033[0m') dev_log='設備總數 %s ,成功備份的設備數量 %s .'%(devicenum,all_success_backup_count) all_exe_detail_log+=(TimeOps.get_time_stamp()+','+dev_log+'\n') all_exe_result_log+=(dev_log+'\n') print('設備總數 \033[1m%s \033[0m,成功備份的設備數量 \033[1m%s \033[0m.'%(devicenum,all_success_backup_count)) if all_fail_backup_count > 0 :#備份失敗設備信息 all_fail_backup_log='\n#備份失敗的設備數量: %s ,具體以下: '%all_fail_backup_count all_exe_detail_log += (TimeOps.get_time_stamp() + ',' + all_fail_backup_log+'\n') all_exe_result_log += (all_fail_backup_log+'\n') print('\n#\033[1;31m備份失敗\033[0m的設備數量: \033[1;31m%s\033[0m ,具體以下: '%all_fail_backup_count) for i in all_fail_backup_context: print('\t',i) all_exe_detail_log += (TimeOps.get_time_stamp() + ',' + '\t'+i+'\n') all_exe_result_log += ('\t'+i+'\n') if all_fail_login_count > 0 :#登陸失敗設備信息 all_fail_login_log='登陸失敗的設備數量: %s ,具體以下: '%all_fail_login_count all_exe_detail_log += (TimeOps.get_time_stamp() + ',' + all_fail_login_log+'\n') all_exe_result_log += (all_fail_login_log+'\n') print('\033[1;31m登陸失敗\033[0m的設備數量: \033[1;31m%s\033[0m ,具體以下: '%all_fail_login_count) for i in all_fail_login_context: print('\t',i) all_exe_detail_log += (TimeOps.get_time_stamp() + ',' + '\t'+i+'\n') all_exe_result_log += ('\t'+i+'\n') if devicelistErr_count > 0 :#設備列表參數錯誤 dev_err_log='在devicelst.csv文件中,有 %s 行參數錯誤,具體以下:'%devicelistErr_count all_exe_detail_log += (TimeOps.get_time_stamp() + ',' + dev_err_log+'\n') all_exe_result_log += (dev_err_log+'\n') print('在devicelst.csv文件中,有 \033[1;31m%s 行參數錯誤\033[0m,具體以下:'%devicelistErr_count) for i in devicelistErr_context: list=i.split(',')#將每行的str轉換爲list[] if len(list) >= 6:#由於devicelistErr_context裏多了Devicelist第 x 行參數不對或者空行', '行信息以下:,裏面有一個‘,’,所以+1 list[6]='******'#將密碼加密 i=','.join(list)#從新轉換成str print('\t',i) all_exe_detail_log += (TimeOps.get_time_stamp() + ',' + '\t'+i+'\n') all_exe_result_log += ('\t'+i+'\n') end_time = time.time() end_log='\n程序運行總耗時: %s 秒。'%(int(end_time - start_time)) all_exe_detail_log += (TimeOps.get_time_stamp() + ',' + end_log+'\n') all_exe_result_log += (end_log+'\n') FileOps.writelog(all_exe_detail_log,'detail') FileOps.writelog(all_exe_result_log, 'result') print(end_log) if __name__ == "__main__": main('devicelist.csv') #主函數,參數爲設備列表名稱