通过几天折腾,终于搞出来了一个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') #主函数,参数为设备列表名称