Posts 网络自动化运维之H3C交换机
Post
Cancel

网络自动化运维之H3C交换机

用知识的土壤来填埋这些坑洞

Netmiko模块

主要用于与网络设备之间的ssh连接,并提供了世界主流网络设备厂商的适配,使对网络设备的配置工作能够自动化实现。

send_command():只支持向设备发送一条命令,通常是show/display之类的查询、排错命令或者wr mem这样保存配置的命令。发出命令后,默认情况下这个函数会一直等待,直到接收到设备的完整回显内容为止(以收到设备提示符为准,比如说要一直等到读取到“#”为止),如果在一定时间内依然没读到完整的回显内容,netmiko则会返回一个OSError: Search pattern never detected in send_command: xxxxx的异常。

如果想要指定netmiko从回显内容中读到我们需要的内容,则需要用到expect_string参数(expect_string默认值为None),如果send_command()从回显内容中读到了expect_string参数指定的内容,则send_command()依然返回完整的回显内容,如果没读到expect_string参数指定的内容,则netmiko同样会返回一个OSError: Search pattern never detected in send_command: xxxxx的异常

正则表达式(regular expression):

描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

\w{4} 指的是匹配任意一个包括下划线的任何单词字符,等价于’[A-Za-z0-9_]’或者一个字符,可以重复的集合。

compile()与search()搭配使用, 匹配不到会返回None,返回None的时候就没有span/group属性了, 可以不从位置0开始匹配。但是匹配一个单词之后,匹配就会结束。

故障现象

之前收到反馈,交换机console账户密码批量修改脚本运行出错,查看错误日志,发现在发送user-interface aux 0 - 1配置命令时,多了个“-”符号,从而引发错误。 进行了源码分析,确定该bug现象是由于该发送配置命令的逻辑运算出现了运算错误,该错误是因netmiko模块的send_command函数在进行命令配置后,读取不到完整的回显内容,但却未触发OSError错误告警,继续执行,在后续的正则表达式匹配中,由于回显内容不足,导致运算信息欠缺,从而引起bug。

解决方法

在send_command函数增加指定的内容验证,使其读取到命令执行完毕后,回到用户界面,出现的“>”字符才结束,否则将触发OSError错误告警,保证最终回显信息完整。

网络设备自动化脚本里的一部分功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
password = ConfigParser()   
password.read('xxxx.ini')
orderpass = (xxxx['xxxx']['xxxx'])
passLength = 8  # 密码乱序随机生成部分的长度设置

timeName = time.strftime('%Y-%m-%d-%H%M%S')  
#%Y 以世纪为十进制数的年份。
#%m 月份为十进制数 [01,12]。 
#%d 十进制数 [01,31] 月份中的第几天。 
#%H 小时(24 小时制),十进制数 [00,23]。 
#%M 十进制数的分钟 [00,59]。 
#%S 秒为十进制数 [00,61]。

outpath = f'{timeName}COM密码修改结果日志/日志/'  
outpath_exl = f'{timeName}COM密码修改结果日志/结果.xls'  
header = ['ip', 'COM新密码', '结果', '错误原因'] 

res = {}  
errlist = []  
pattern_mac = re.compile(r'(\w{4}-\w{4}-\w{4})')  #正则表达示匹配MAC
super_bit = 0  
threadList = []  

def randumPassGen():  
	new_pass = random.sample(string.ascii_letters + string.digits + '!@#$%^&*()', passLength)
	fi_pass = ''.join(new_pass)
	fi_pass = orderpass + fi_pass  
	return fi_pass

def runscript(row):
	ip = row['ip']
	h3c = {
		'device_type': 'hp_comware',
		'host': row['ip'],
		'username': row['用户名'],
		'password': str(row['密码'])
	}
	if cmds_bit == 0: 
		newPwd = randumPassGen()
	elif cmds_bit == 1:  
		newPwd = str(row['指定console密码'])
	try:
		conn = ConnectHandler(**h3c)  
        #ConnectHandler()函数需要在h3c字典里面找"两"个东西,key和对应的value,所以用两个*星号
	except ssh_exception.NetmikoAuthenticationException:
		s = {'ip': ip, 'COM新密码': '无变动', '结果': '失败', '错误原因': '登录密码错误'}
		print(f'{ip}登录密码错误!!!')
	except ssh_exception.NetmikoTimeoutException:
		s = {'ip': ip, 'COM新密码': '无变动', '结果': '失败', '错误原因': '设备不可达'}
		print(f'{ip}设备IP不可达!!!!!')
	else:
		print(f'{ip}已成功登陆,请等待下一步操作~')
		out = ''
		out += conn.send_command('\ndisp irf', strip_prompt=False, strip_command=False)  #向设备发送'\ndisp irf'
		with open(f'{outpath}{ip}.txt', 'w') as stream:
			for line in out:
				try:
					stream.write(line)
				except:
					pass
		com_ounter = -1    #因为irf会有一个irf的mac,所以需要减1
		if '^' in out:      #判断 irf命令是否有错误,h3c错误会有^图标
			com_ounter = 1
		else:
			log = out.splitlines()   #过滤out的('\r', '\r\n', \n'),默认false,不包含。
			for line in log:   #遍历每行
				mac = re.search(pattern_mac, line)    #匹配正则mac,没有会返回None
				if mac:    
         #在python中 None, False, 空字符串"", 0, 空列表[], 空字典{}, 空元组()都相当于False 
					com_ounter += 1  
		print(f'{ip}查询COM数量为{com_ounter}!')    
		if com_ounter==1:
			cmdlists=[
			'sys',
			'user-interface aux 0',
			f'set authentication password simple {newPwd}',
			f'set authentication password cipher {newPwd}',
			'return',]
		else:
			cmdlists=[
			'sys',
			f'user-interface aux 0 {com_ounter-1}',
			f'set authentication password simple {newPwd}',
			f'set authentication password cipher {newPwd}',
			'return',]
		try:
			if super_bit:
				out += conn.send_command('super', expect_string='password:', strip_prompt=False, strip_command=False)
				out += conn.send_command(str(row['super密码']), expect_string='>', strip_prompt=False, strip_command=False)
			for cmd in cmdlists:
				out += conn.send_command(cmd, expect_string='>|]', strip_prompt=False, strip_command=False)  #> 或运算 ]
			out += conn.send_command_timing('save f', delay_factor=0.1, max_loops=5,strip_prompt=False, strip_command=False) 
		except Exception as e:   #以上netmiko的函数出现错误都会反馈OSError
			s = {'ip': ip, 'COM新密码': '无变动', '结果': '失败', '错误原因': '命令执行过程出错:' + repr(e)} #repr() 函数将对象转化为供解释器读取的形式。
			print(f'命令执行过程出错:{ip}' + repr(e))
		else:
			s = {'ip': ip, 'COM新密码': newPwd, '结果': '成功', '错误原因': '无'}
			print(f'{ip}设备COM密码修改成功!!!!' )
		finally:
			with open(f'{outpath}{ip}.txt', 'w') as stream:
				for line in out:
					try:
						stream.write(line)
					except:
						pass
	finally:   
		res[ip] = s
		try:
			conn.disconnect()
		except:
			pass

参考资料

netmiko network devices
Netmiko详解
正则表达式
Netmiko解析

This post is licensed under CC BY 4.0 by the author.

Contents

记录一次绿盟WAF误判SQL injection排查

记一次web自动化运维脚本排错-selenium

Comments powered by Disqus.