blob: 40148aba2986bf0b841968adf47bf9719945fec5 [file] [log] [blame]
A R Karthickd0fdf3b2017-03-21 16:54:22 -07001import os
2import shutil
3import re
4from novaclient import client as nova_client
5from SSHTestAgent import SSHTestAgent
6from CordTestUtils import *
A R Karthick933f5b52017-03-27 15:27:16 -07007from CordTestUtils import log_test as log
8
9log.setLevel('INFO')
A R Karthickd0fdf3b2017-03-21 16:54:22 -070010
11class VSGAccess(object):
12
13 vcpe_map = {}
14 interface_map = {}
15 ip_addr_pattern = re.compile('[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$')
16
17 @classmethod
18 def setUp(cls):
19 try:
20 shutil.copy('/etc/resolv.conf', '/etc/resolv.conf.orig')
21 except:
22 pass
23
24 @classmethod
25 def tearDown(cls):
26 try:
27 shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
28 except:
29 pass
30
Chetan Gaonker79b35532017-03-31 18:58:36 +000031 '''
32 @method: get_nova_credentials_v2
33 @Description: Get nova credentials
34 @params:
35 returns credential from env
36 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -070037 @classmethod
38 def get_nova_credentials_v2(cls):
39 credential = {}
40 credential['username'] = os.environ['OS_USERNAME']
41 credential['api_key'] = os.environ['OS_PASSWORD']
42 credential['auth_url'] = os.environ['OS_AUTH_URL']
43 credential['project_id'] = os.environ['OS_TENANT_NAME']
44 return credential
45
Chetan Gaonker79b35532017-03-31 18:58:36 +000046 '''
47 @method: get_compute_nodes
48 @Description: Get the list of compute nodes
49 @params:
50 returns node list
51 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -070052 @classmethod
53 def get_compute_nodes(cls):
54 credentials = cls.get_nova_credentials_v2()
55 nvclient = nova_client.Client('2', **credentials)
56 return nvclient.hypervisors.list()
57
Chetan Gaonker79b35532017-03-31 18:58:36 +000058 '''
59 @method: get_vsgs
60 @Description: Get list of vsg's running in compute node
61 @params: status of vsg
62 returns vsg wrappers
63 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -070064 @classmethod
65 def get_vsgs(cls, active = True):
66 credentials = cls.get_nova_credentials_v2()
67 nvclient = nova_client.Client('2', **credentials)
68 vsgs = nvclient.servers.list(search_opts = {'all_tenants': 1})
69 if active is True:
70 vsgs = filter(lambda vsg: vsg.status == 'ACTIVE', vsgs)
71 vsg_wrappers = []
72 for vsg in vsgs:
73 vsg_wrappers.append(VSGWrapper(vsg))
74 return vsg_wrappers
75
Chetan Gaonker79b35532017-03-31 18:58:36 +000076 '''
77 @method: open_mgmt
78 @Description: Bringing up Interface for access to management
79 @params: intf = "Interface to open"
80 returns Gateway
81 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -070082 @classmethod
83 def open_mgmt(cls, intf = 'eth0'):
84 if intf in cls.interface_map:
85 gw = cls.interface_map[intf]['gw']
86 ip = cls.interface_map[intf]['ip']
87 if gw != '0.0.0.0':
88 current_gw, _ = get_default_gw()
89 cmds = [ 'route del default gw {}'.format(current_gw),
90 'ifconfig {} {} up'.format(intf, ip),
91 'route add default gw {}'.format(gw) ]
92 for cmd in cmds:
93 os.system(cmd)
94 shutil.copy('/etc/resolv.conf', '/etc/resolv.conf.lastdhcp')
95 shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
96 return current_gw
97 return None
98
Chetan Gaonker79b35532017-03-31 18:58:36 +000099 '''
100 @method: close_mgmt
101 @Description: Bringing up gateway deleting default
102 @params: intf = "Interface to open"
103 dict2 = retrieved data from GET method
104 returns: NA
105 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700106 @classmethod
107 def close_mgmt(cls, restore_gw, intf = 'eth0'):
108 if restore_gw:
109 cmds = [ 'route del default gw 0.0.0.0',
110 'route add default gw {}'.format(restore_gw),
111 'cp /etc/resolv.conf.lastdhcp /etc/resolv.conf',
112 'rm -f /etc/resolv.conf.lastdhcp'
113 ]
114 for cmd in cmds:
115 os.system(cmd)
116
Chetan Gaonker79b35532017-03-31 18:58:36 +0000117 '''
118 @method: health_check
119 @Description: Check if vsgs are reachable
120 @params:
121 returns True
122 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700123 @classmethod
124 def health_check(cls):
125 '''Returns 0 if all active vsgs are reachable through the compute node'''
126 vsgs = cls.get_vsgs()
127 vsg_status = []
128 for vsg in vsgs:
129 vsg_status.append(vsg.get_health())
130 unreachable = filter(lambda st: st == False, vsg_status)
131 return len(unreachable) == 0
132
Chetan Gaonker79b35532017-03-31 18:58:36 +0000133 '''
134 @method: get_vcpe_vsg
135 @Description: Getting vsg vm instance info from given vcpe
136 @params: vcpe = "vcpe name"
137 returns vsg
138 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700139 @classmethod
140 def get_vcpe_vsg(cls, vcpe):
141 '''Find the vsg hosting the vcpe service'''
142 if vcpe in cls.vcpe_map:
143 return cls.vcpe_map[vcpe]['vsg']
144 vsgs = cls.get_vsgs()
145 for vsg in vsgs:
146 cmd = 'sudo docker exec {} ls 2>/dev/null'.format(vcpe)
147 st, _ = vsg.run_cmd(cmd, timeout = 30)
148 if st == True:
149 return vsg
150 return None
151
Chetan Gaonker79b35532017-03-31 18:58:36 +0000152 '''
153 @method: save_vcpe_config
154 @Description: Saving vcpe config with lan & wan side info
155 @params: vsg
156 vcpe
157 returns True
158 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700159 @classmethod
160 def save_vcpe_config(cls, vsg, vcpe):
161 if vcpe not in cls.vcpe_map:
162 cmd_gw = "sudo docker exec %s ip route show | grep default | head -1 | awk '{print $3}'" %(vcpe)
163 vsg_ip = vsg.ip
164 if vsg_ip is None:
165 return False
166 st, output = vsg.run_cmd(cmd_gw, timeout = 30)
167 if st == False or not output:
168 return False
169 gw = output
170 cmd_wan = "sudo docker exec %s ip addr show eth0 |grep inet |head -1 | tr -s ' ' | awk '{print $2}' | awk '{print $1}'" %(vcpe)
171 cmd_lan = "sudo docker exec %s ip addr show eth1 |grep inet |head -1 | tr -s ' ' | awk '{print $2}' | awk '{print $1}'" %(vcpe)
172 st, output = vsg.run_cmd(cmd_wan, timeout = 30)
173 ip_wan = '0.0.0.0/24'
174 ip_lan = '0.0.0.0/24'
175 if st and output:
176 if cls.ip_addr_pattern.match(output):
177 ip_wan = output
178
179 st, output = vsg.run_cmd(cmd_lan, timeout = 30)
180 if st and output:
181 if cls.ip_addr_pattern.match(output):
182 ip_lan = output
183
184 cls.vcpe_map[vcpe] = { 'vsg': vsg, 'gw': gw, 'wan': ip_wan, 'lan': ip_lan }
185
186 return True
187
Chetan Gaonker79b35532017-03-31 18:58:36 +0000188 '''
189 @method: restore_vcpe_config
190 @Description: Restoring saved config for lan & wan
191 @params: vcpe
192 gw
193 wan
194 lan
195 returns True/False
196 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700197 @classmethod
198 def restore_vcpe_config(cls, vcpe, gw = True, wan = False, lan = False):
199 if vcpe in cls.vcpe_map:
200 vsg = cls.vcpe_map[vcpe]['vsg']
201 cmds = []
202 if gw is True:
203 #restore default gw
204 gw = cls.vcpe_map[vcpe]['gw']
205 cmds.append('sudo docker exec {} ip link set eth0 up'.format(vcpe))
206 cmds.append('sudo docker exec {} route add default gw {} dev eth0'.format(vcpe, gw))
207 if wan is True:
208 ip_wan = cls.vcpe_map[vcpe]['wan']
209 cmds.append('sudo docker exec {} ip addr set {} dev eth0'.format(vcpe, ip_wan))
210 if lan is True:
211 ip_lan = cls.vcpe_map[vcpe]['lan']
212 cmds.append('sudo docker exec {} ip addr set {} dev eth1'.format(vcpe, ip_lan))
213 ret_status = True
214 for cmd in cmds:
215 st, _ = vsg.run_cmd(cmd, timeout = 30)
216 if st == False:
217 ret_status = False
218 return ret_status
219 return False
220
Chetan Gaonker79b35532017-03-31 18:58:36 +0000221 '''
222 @method: get_vcpe_gw
223 @Description: Get gw of vcpe from created map
224 @params: vcpe
225 returns gw
226 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700227 @classmethod
228 def get_vcpe_gw(cls, vcpe):
229 if vcpe in cls.vcpe_map:
230 return cls.vcpe_map[vcpe]['gw']
231 return None
232
Chetan Gaonker79b35532017-03-31 18:58:36 +0000233 '''
234 @method: get_vcpe_wan
235 @Description:
236 @params:
237 return wan side of vcpe
238 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700239 @classmethod
240 def get_vcpe_wan(cls, vcpe):
241 if vcpe in cls.vcpe_map:
242 return cls.vcpe_map[vcpe]['wan']
243 return None
244
Chetan Gaonker79b35532017-03-31 18:58:36 +0000245 '''
246 @method: get_vcpe_lan
247 @Description:
248 @params:
249 returns True if contents of dict1 exists in dict2
250 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700251 @classmethod
252 def get_vcpe_lan(cls, vcpe):
253 if vcpe in cls.vcpe_map:
254 return cls.vcpe_map[vcpe]['lan']
255 return None
256
Chetan Gaonker79b35532017-03-31 18:58:36 +0000257 '''
258 @method: vcpe_wan_up
259 @Description:
260 @params:
261 returns status
262 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700263 @classmethod
264 def vcpe_wan_up(cls, vcpe):
265 return cls.restore_vcpe_config(vcpe)
266
Chetan Gaonker79b35532017-03-31 18:58:36 +0000267 '''
268 @method: vcpe_lan_up
269 @Description:
270 @params:
271 returns status
272 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700273 @classmethod
274 def vcpe_lan_up(cls, vcpe, vsg = None):
275 if vsg is None:
276 vsg = cls.get_vcpe_vsg(vcpe)
277 if vsg is None:
278 return False
279 cmd = 'sudo docker exec {} ip link set eth1 up'.format(vcpe)
280 st, _ = vsg.run_cmd(cmd, timeout = 30)
281 return st
282
Chetan Gaonker79b35532017-03-31 18:58:36 +0000283 '''
284 @method: vcpe_port_down
285 @Description:
286 @params:
287 returns status
288 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700289 #we cannot access compute node if the vcpe port gets dhcp as default would be through fabric
290 @classmethod
291 def vcpe_port_down(cls, vcpe, port, vsg = None):
292 if vsg is None:
293 vsg = cls.get_vcpe_vsg(vcpe)
294 if vsg is None:
295 return False
296 if not cls.save_vcpe_config(vsg, vcpe):
297 return False
298 cmd = 'sudo docker exec {} ip link set {} down'.format(vcpe, port)
299 st, _ = vsg.run_cmd(cmd, timeout = 30)
300 if st is False:
301 cls.restore_vcpe_config(vcpe)
302 return False
303 return st
304
Chetan Gaonker79b35532017-03-31 18:58:36 +0000305 '''
306 @method: vcpe_wan_down
307 @Description:
308 @params:
309 returns status
310 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700311 @classmethod
312 def vcpe_wan_down(cls, vcpe, vsg = None):
313 return cls.vcpe_port_down(vcpe, 'eth0', vsg = vsg)
314
Chetan Gaonker79b35532017-03-31 18:58:36 +0000315 '''
316 @method: vcpe_lan_down
317 @Description:
318 @params:
319 returns status
320 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700321 @classmethod
322 def vcpe_lan_down(cls, vcpe, vsg = None):
323 return cls.vcpe_port_down(vcpe, 'eth1', vsg = vsg)
324
Chetan Gaonker79b35532017-03-31 18:58:36 +0000325 '''
326 @method: save_interface_config
327 @Description:
328 @params:
329 returns NA
330 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700331 @classmethod
332 def save_interface_config(cls, intf):
333 if intf not in cls.interface_map:
334 ip = get_ip(intf)
335 if ip is None:
336 ip = '0.0.0.0'
337 default_gw, default_gw_device = get_default_gw()
338 if default_gw_device != intf:
339 default_gw = '0.0.0.0'
340 cls.interface_map[intf] = { 'ip' : ip, 'gw': default_gw }
341 #bounce the interface to remove default gw
342 cmds = ['ifconfig {} 0 down'.format(intf),
343 'ifconfig {} 0 up'.format(intf)
344 ]
345 for cmd in cmds:
346 os.system(cmd)
347
Chetan Gaonker79b35532017-03-31 18:58:36 +0000348 '''
349 @method: restore_interface_config
350 @Description:
351 @params:
352 returns NA
353 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700354 #open up access to compute node
355 @classmethod
356 def restore_interface_config(cls, intf, vcpe = None):
357 if intf in cls.interface_map:
358 ip = cls.interface_map[intf]['ip']
359 gw = cls.interface_map[intf]['gw']
360 del cls.interface_map[intf]
361 cmds = []
362 if vcpe is not None:
363 shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
364 #bounce the vcpes to clear default gw
365 cmds.append('ifconfig {} 0 down'.format(vcpe))
366 cmds.append('ifconfig {} 0 up'.format(vcpe))
367 cmds.append('ifconfig {} {} up'.format(intf, ip))
368 if gw and gw != '0.0.0.0':
369 cmds.append('route add default gw {} dev {}'.format(gw, intf))
370 for cmd in cmds:
371 os.system(cmd)
372
Chetan Gaonker79b35532017-03-31 18:58:36 +0000373 '''
374 @method: vcpe_get_dhcp
375 @Description: Get DHCP from vcpe dhcp interface.
376 @params:
377 returns vcpe ip
378 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700379 @classmethod
380 def vcpe_get_dhcp(cls, vcpe, mgmt = 'eth0'):
381 '''Get DHCP from vcpe dhcp interface.'''
382 '''We have to also save the management interface config for restoration'''
383 cls.save_interface_config(mgmt)
384 getstatusoutput('pkill -9 dhclient')
385 st, output = getstatusoutput('dhclient -q {}'.format(vcpe))
386 getstatusoutput('pkill -9 dhclient')
387 vcpe_ip = get_ip(vcpe)
388 if vcpe_ip is None:
389 cls.restore_interface_config(mgmt)
390 return None
391 if output:
392 #workaround for docker container apparmor that prevents moving dhclient resolv.conf
393 start = output.find('/etc/resolv.conf')
394 if start >= 0:
395 end = output.find("'", start)
396 dns_file = output[start:end]
397 if os.access(dns_file, os.F_OK):
398 shutil.copy(dns_file, '/etc/resolv.conf')
399
400 default_gw, default_gw_device = get_default_gw()
401 if default_gw and default_gw_device == vcpe:
402 return vcpe_ip
403 cls.restore_interface_config(mgmt, vcpe = vcpe)
404 return None
405
406class VSGWrapper(object):
407
408 def __init__(self, vsg):
409 self.vsg = vsg
410 self.name = self.vsg.name
411 self.compute_node = self.get_compute_node()
412 self.ip = self.get_ip()
413
Chetan Gaonker79b35532017-03-31 18:58:36 +0000414 '''
415 @method: get_compute_node
416 @Description:
417 @params:
418 returns compute node name
419 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700420 def get_compute_node(self):
421 return self.vsg._info['OS-EXT-SRV-ATTR:hypervisor_hostname']
422
Chetan Gaonker79b35532017-03-31 18:58:36 +0000423 '''
424 @method: get_ip
425 @Description:
426 @params:
427 returns ip of network
428 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700429 def get_ip(self):
430 if 'management' in self.vsg.networks:
431 ips = self.vsg.networks['management']
432 if len(ips) > 0:
433 return ips[0]
434 return None
435
Chetan Gaonker79b35532017-03-31 18:58:36 +0000436 '''
437 @method: run_cmd_compute
438 @Description:
439 @params:
440 returns Status & output
441 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700442 def run_cmd_compute(self, cmd, timeout = 5):
443 ssh_agent = SSHTestAgent(self.compute_node)
444 st, output = ssh_agent.run_cmd(cmd, timeout = timeout)
445 if st == True and output:
446 output = output.strip()
447 else:
448 output = None
449
450 return st, output
451
Chetan Gaonker79b35532017-03-31 18:58:36 +0000452 '''
453 @method: run_cmd
454 @Description:
455 @params:
456 returns status & output
457 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700458 def run_cmd(self, cmd, timeout = 5, mgmt = 'eth0'):
459 last_gw = VSGAccess.open_mgmt(mgmt)
460 ssh_agent = SSHTestAgent(self.compute_node)
461 ssh_cmd = 'ssh {} {}'.format(self.ip, cmd)
462 st, output = ssh_agent.run_cmd(ssh_cmd, timeout = timeout)
463 if st == True and output:
464 output = output.strip()
465 else:
466 output = None
467 VSGAccess.close_mgmt(last_gw, mgmt)
468 return st, output
469
Chetan Gaonker79b35532017-03-31 18:58:36 +0000470 '''
471 @method: get_health
472 @Description:
473 @params:
474 returns Status
475 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700476 def get_health(self):
477 if self.ip is None:
A R Karthick850795b2017-03-27 14:07:03 -0700478 return True
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700479 cmd = 'ping -c 1 {}'.format(self.ip)
A R Karthick933f5b52017-03-27 15:27:16 -0700480 log.info('Pinging VSG %s at IP %s' %(self.name, self.ip))
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700481 st, _ = self.run_cmd_compute(cmd)
A R Karthick933f5b52017-03-27 15:27:16 -0700482 log.info('VSG %s at IP %s is %s' %(self.name, self.ip, 'reachable' if st == True else 'unreachable'))
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700483 return st
484
Chetan Gaonker79b35532017-03-31 18:58:36 +0000485 '''
486 @method: check_access
487 @Description: validates access
488 @params:
489 returns Status
490 '''
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700491 def check_access(self):
492 if self.ip is None:
A R Karthick933f5b52017-03-27 15:27:16 -0700493 return True
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700494 ssh_agent = SSHTestAgent(self.compute_node)
495 st, _ = ssh_agent.run_cmd('ls', timeout=10)
496 if st == False:
A R Karthick933f5b52017-03-27 15:27:16 -0700497 log.error('Compute node at %s is not accessible' %(self.compute_node))
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700498 return st
A R Karthick933f5b52017-03-27 15:27:16 -0700499 log.info('Checking if VSG at %s is accessible from compute node %s' %(self.ip, self.compute_node))
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700500 st, _ = ssh_agent.run_cmd('ssh {} ls'.format(self.ip), timeout=30)
A R Karthick933f5b52017-03-27 15:27:16 -0700501 if st == True:
502 log.info('OK')
A R Karthickd0fdf3b2017-03-21 16:54:22 -0700503 return st